JS - Getting either text or JSON with Fetch API - javascript

I am moving over from jQuery AJAX requests to the new Fetch API (nothing against jQuery, I still have it in my site, but Fetch looks - according to Jake Archibald and David Walsh and also IMHO - to be the new way of sending async requests).
As such, with jQuery, I had the following function (more or less):
function ajaxCall(type, url, data) {
return $.ajax({
type: type,
url: url,
data: data,
})
.fail(function(xhr, status, errorThrown) {
// Do fail stuff
})
.always(function(xhr, status) {
// Do always stuff
});
}
// Later...
var myAjax = ajaxCall(myType, myUrl, myData);
myAjax.done(function(xhr) {
// Do done stuff
});
This way, I could have one function be called to handle any and all ajax requests I could ever need (for the most part at least...). Note that I do not declare a dataType, as I use jQuery's intelligent guess. This way my server can send me whatever response and I could handle it (probably a smarter way to do this would be to pass another parameter with the data type - in the case the "intelligent guess" goes wrong, but this was the way I set it up).
I am now trying to recreate the above with the new Fetch API. What I have so far currently looks like this:
function fetchCall(url, method, body) {
// This if statement is supposed to handle
// query selectors (which in GET requests go in the url)
// on GET requests - as opposed to POST req's which go in the body
if (method === 'GET') {
var data = body;
url = new URL(url, location.protocol + '//' + location.host + '/');
Object.keys(data).forEach(key => url.searchParams.append(key, data[key]));
body = undefined;
}
return fetch(url, {
method: method,
body: body
}).then(function(res) {
if (res.ok) return res;
throw new Error('Server error. Status code: ', res.status);
}).catch(function(err) {
console.log(err);
});
}
// Later...
var myFetch = fetchCall(myUrl, myMethod, myBody);
myFetch.then(function(res) {
console.log(res);
});
The problem I am running into is that if res.ok return res; does not state what type of response it is (i.e. res.json(), res.blob(), res.text(), etc.).
Thus, I am wondering how to set up a dynamic way of setting the type of response body. Is this even possible at the Fetch API's current state of development? Is it just that there is something I am not duplicating in MDN?
After messing around with this, I also realized I could make it always set to return res.text(); and the if the call is supposed to be JSON, use JSON.parse(response);, but I do want it to be dynamic. What if I end up wanting to return a blob()?

So, as far as the conversation has reached, there is a way to understand what type of content has been received, with two remarks:
Typically you have to always know and expect exact content type, and a universal solution is rather odd in case of fetching from a certain remote endpoint, and
The Content-Type header is what will tell you the type of content received, but the server may send a wrong header, which is very unusual to happen and therefore is negligible.
The Response object has header property that is (kind of) a Map, so you can use its get method to get a value by key.
The easiest and cleanest way to check if the returned value is a certain MIME type you expect is by using a regular expression:
// replace url with the actual API endpoint URL
fetch(url).then(response => {
const contentType = response.headers.get('Content-Type'); // -> "text/html; charset=utf-8"
if (/text\/html/i.test(contentType)) {
// do something when the Content-Type is text/html
} else if (/application\/json/.test(contentType)) {
// do something when the Content-Type is application/json
}
// and so on, for every Content-Type you need.
}).catch(error => {
// do something when error happens
});

Related

Connect to an API with URL Login and Password in Typescript / Javascript

I'm new to Web programming, and I wanted to know how to make easy requests to connect to an API if the only information I have are the URL, a login and password using pure Typescript or Javascript.
The goal is to retrieve an access Token.
I would advise using the browsers fetch API which uses promises.
let us begin here. In the past browsers offered AJAX calls but ever since modern JS came through with promises, everything got really easy. You can find out more about promises here.
let us consider an example similar to your case where you have a url endpoint, and some data you want to send to the url. we expect that the url with respond with a JSON payload containing a token.
// so we will define our constants here
const url = 'http://localhost/login';
const username = 'username';
const password = 'password';
// now we configure the fetch request to the url endpoint.
// we should probably put it inside a separate function since
// you're using a browser, you probably will bind this request
// to a click event or something.
function login() {
return fetch(url, {
// in the case of a login request most APIs use the POST method offered by
// RESTful APIs
method: 'post', // can be 'get', 'put', 'delete', and many more
// now we set any needed headers specified by the API
headers: {
// most APIs I have worked with use
'Content-Type': 'application/json',
// but some might need more, they will specify anyway.
},
// because we are using the 'post' method then we will need to add
// a body to the request with all our data, body excepts a string so
// we do the following
body: JSON.stringify({
username: username,
password: password,
}),
})
// Now we handle the response because the function returns a promise
.then((response) => {
// An important thing to note is that an error response will not throw
// an error so if the result is not okay we should throw the error
if(!response.ok) {
throw response;
}
// since we expect a json response we will return a json call
return response.json();
})
}
now since you are using a browser you will have to bind the function to
an element to trigger the call
// will define a self calling arrow function that will do the event binding
((button) => {
button.addEventListener('click', (event) => {
// we then call the function here.
login()
.then((response) => {
// side note, you could destructure the response argument as { token }
// then just reference token instead.
// recall that we expect this function to have a 'token' key in
// the response payload...so let us log it just to make sure
console.log(result.token);
})
});
// we add the button reference as the argument
})(document.querySelector('#submit'));
References:
Promises
RESTful
I hope this helps give a better idea on the task you have, good luck
u can use axios for js/ts
like this example :
getToken = () => {
axios.post('http://localhost/login',{
username: username,
password: password,
}).then((response) => {
let data = response.data;
//do whatever u want here eg :set token to local storage
localStorage.set('token',data.token);
}).catch((error) => {
console.log(error);
});
};
getToken();

Returning Data from express server | function call?

I am trying to return a response from a backend server, of technologies that match a specific url which is searched on the front end.
My form will submit, and the URL will change, but no results will come through, and no errors appear in the console.
My front end script tag is as follows:
function Submit () {
e.preventDefault();
const formResult = new FormData(form);
const url = '/' + encodeURIComponent(formResult.get('search'));
const button = document.getElementById('search');
const sendHttpsRequest = (method, url, data) => {
return fetch(url, {
method: method,
body: JSON.stringify(data),
headers: data ? {'Content-Type': 'application/json'} : {}
}).then(response => {
if (response.status >= 400) { //response okay
return response.json().then(errResData => {
const error = new Error('Something went wrong');
error.data = errResData;
throw error;
});
}
return response.json();
});
};
const getData = () => {
sendHttpsRequest('GET', '/:url').then(responseData => {
console.log(responseData);
});
};
button.addEventListener('search', getData);
}
Is there something here I am missing? I think it might be a function call, but do I also need to include a function call when I am sending an HTTP request? Where would the function call go in this case? What would it look like?
You have lots of problems with this.
You call getData when a button is clicked, but only if that button is clicked after Search is called. You never call Search.
The first thing Search does is call e.preventDefault, but e isn't defined anywhere.
You create formResult from a variable called form which you never define
You create formResult when Submit is called but it looks more like something you want to do when getData is called
You set the body of the request with JSON.stringify(data), but you've never defined data … and if you intended to say formResult then that would be a FormData object which won't convert to JSON.
When you send the request you sent it to '/:url' which uses Express parameter syntax … but this is the client-side code so you need to put the actual data there.
You define a variable url in Submit but then you never use it (probably you intended to use it in 6 but forgot and hardcoded a string that didn't make sense instead).
You are trying to set a body on a GET request
You seem to have thrown a lot of code together without testing it as you went along.
I recommend starting from scratch.
Write a function to be called when you click the button. Have it just prevent the default behaviour and log something to the console.
Then fetch the form data you want and log that.
Continue to work from there, testing each stage as you go along so you can detect the mistakes like these.

Cloudflare Worker TypeError: One-time-use body

I'm trying to use a Cloudflare Worker to proxy a POST request to another server.
It is throwing a JS exception – by wrapping in a try/catch blog I've established that the error is:
TypeError: A request with a one-time-use body (it was initialized from a stream, not a buffer) encountered a redirect requiring the body to be retransmitted. To avoid this error in the future, construct this request from a buffer-like body initializer.
I would have thought this could be solved by simply copying the Response so that it's unused, like so:
return new Response(response.body, { headers: response.headers })
That's not working. What am I missing about streaming vs buffering here?
addEventListener('fetch', event => {
var url = new URL(event.request.url);
if (url.pathname.startsWith('/blog') || url.pathname === '/blog') {
if (reqType === 'POST') {
event.respondWith(handleBlogPost(event, url));
} else {
handleBlog(event, url);
}
} else {
event.respondWith(fetch(event.request));
}
})
async function handleBlog(event, url) {
var newBlog = "https://foo.com";
var originUrl = url.toString().replace(
'https://www.bar.com/blog', newBlog);
event.respondWith(fetch(originUrl));
}
async function handleBlogPost(event, url) {
try {
var newBlog = "https://foo.com";
var srcUrl = "https://www.bar.com/blog";
const init = {
method: 'POST',
headers: event.request.headers,
body: event.request.body
};
var originUrl = url.toString().replace( srcUrl, newBlog );
const response = await fetch(originUrl, init)
return new Response(response.body, { headers: response.headers })
} catch (err) {
// Display the error stack.
return new Response(err.stack || err)
}
}
A few issues here.
First, the error message is about the request body, not the response body.
By default, Request and Response objects received from the network have streaming bodies -- request.body and response.body both have type ReadableStream. When you forward them on, the body streams through -- chunks are received from the sender and forwarded to the eventual recipient without keeping a copy locally. Because no copies are kept, the stream can only be sent once.
The problem in your case, though, is that after streaming the request body to the origin server, the origin responded with a 301, 302, 307, or 308 redirect. These redirects require that the client re-transmit the exact same request to the new URL (unlike a 303 redirect, which directs the client to send a GET request to the new URL). But, Cloudflare Workers didn't keep a copy of the request body, so it can't send it again!
You'll notice this problem doesn't happen when you do fetch(event.request), even if the request is a POST. The reason is that event.request's redirect property is set to "manual", meaning that fetch() will not attempt to follow redirects automatically. Instead, fetch() in this case returns the 3xx redirect response itself and lets the application deal with it. If you return that response on to the client browser, the browser will take care of actually following the redirect.
However, in your worker, it appears fetch() is trying to follow the redirect automatically, and producing an error. The reason is that you didn't set the redirect property when you constructed your Request object:
const init = {
method: 'POST',
headers: event.request.headers,
body: event.request.body
};
// ...
await fetch(originUrl, init)
Since init.redirect wasn't set, fetch() uses the default behavior, which is the same as redirect = "automatic", i.e. fetch() tries to follow redirects. If you want fetch() to use manual redirect behavior, you could add redirect: "manual" to init. However, it looks like what you're really trying to do here is copy the whole request. In that case, you should just pass event.request in place of the init structure:
// Copy all properties from event.request *except* URL.
await fetch(originUrl, event.request);
This works because a Request has all of the fields that fetch()'s second parameter wants.
What if you want automatic redirects?
If you really do want fetch() to follow the redirect automatically, then you need to make sure that the request body is buffered rather than streamed, so that it can be sent twice. To do this, you will need to read the whole body into a string or ArrayBuffer, then use that, like:
const init = {
method: 'POST',
headers: event.request.headers,
// Buffer whole body so that it can be redirected later.
body: await event.request.arrayBuffer()
};
// ...
await fetch(originUrl, init)
A note on responses
I would have thought this could be solved by simply copying the Response so that it's unused, like so:
return new Response(response.body, { headers: response.headers })
As described above, the error you're seeing is not related to this code, but I wanted to comment on two issues here anyway to help out.
First, this line of code does not copy all properties of the response. For example, you're missing status and statusText. There are also some more-obscure properties that show up in certain situations (e.g. webSocket, a Cloudflare-specific extension to the spec).
Rather than try to list every property, I again recommend simply passing the old Response object itself as the options structure:
new Response(response.body, response)
The second issue is with your comment about copying. This code copies the Response's metadata, but does not copy the body. That is because response.body is a ReadableStream. This code initializes the new Respnose object to contain a reference to the same ReadableStream. Once anything reads from that stream, the stream is consumed for both Response objects.
Usually, this is fine, because usually, you only need one copy of the response. Typically you are just going to send it to the client. However, there are a few unusual cases where you might want to send the response to two different places. One example is when using the Cache API to cache a copy of the response. You could accomplish this by reading the whole Response into memory, like we did with requests above. However, for responses of non-trivial size, that could waste memory and add latency (you would have to wait for the entire response before any of it gets sent to the client).
Instead, what you really want to do in these unusual cases is "tee" the stream so that each chunk that comes in from the network is actually written to two different outputs (like the Unix tee command, which comes from the idea of a T junction in a pipe).
// ONLY use this when there are TWO destinations for the
// response body!
new Response(response.body.tee(), response)
Or, as a shortcut (when you don't need to modify any headers), you can write:
// ONLY use this when there are TWO destinations for the
// response body!
response.clone()
Confusingly, response.clone() does something completely different from new Response(response.body, response). response.clone() tees the response body, but keeps the Headers immutable (if they were immutable on the original). new Response(response.body, response) shares a reference to the same body stream, but clones the headers and makes them mutable. I personally find this pretty confusing, but it's what the Fetch API standard specifies.

Change query string parametesr of JavaScript requests

Is there a way to change the query string of JavaScript-induced requests? I want to add "&myParam=myValue" to any request sent by my HTML/JS application.
I don't think there's anything built in that lets you do that.
In my apps, I always have a central function XHR goes through so I have a single point to do things like this. If you don't have that or need to intercept calls from 3rd party libs:
You could wrap XMLHttpRequest.open to handle the XHR ones:
var originalOpen = XMLHttpRequest.open;
XMLHttpRequest.open = function() {
var args = Array.prototype.slice.call(arguments);
args[0] += (args[0].indexOf("?") == -1 ? "?" : "&") + "myParam=" + encodeURIComponent("myValue");
return originalOpen.apply(this, args);
};
...and then similar for fetch. But it seems brittle.
Alternately, you might look at using a cookie for the parameter, as the browser will add the cookie to the requests. (That assumes the requests are going to an origina you can add cookies for in your code.)
You could use partial application to lock in defaults when you declare your fetch function and essentially decorate the standard call that will merge your defaults and the passed params.
const fetchFactory = defaults => (url, data) => {
// make a copy of the defaults
const params = Object.assign({}, defaults)
// assign the passed in data with the defaults
params.body = JSON.stringify(Object.assign(params.body, data))
// call fetch with the params
return fetch(url, params)
}
// create a default for POST using the factory
const postFetch = fetchFactory({
method: 'post',
headers: {
'x-requested-with': 'fetch',
'Authorization': 'basic:' + btoa('a secret')
},
body: {
myParam: 'value'
}
})
// now you can call your function
postFetch('http://somewhere.com', {
one: 1,
two: 2
})
.then(respone => response.json())
It seems to me that you are asking how to set/edit URL parameters in http requests. Something quite similar has been asked here: here
If you are using XMLHttpRequest then the accepted answer in the link should work perfectly. The two key things the note are
the url parameters are simply a javascript object that you convert
into a JSON string. This happens through JSON.stringify({ myParam:
'hi'});
the question/answer linked are making post requests but you
may not want to make that request as well, I suggest doing some
research about which HTTP request method you want -
https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html

How can we check if response for the request came from Service Worker

In Google Chrome console next to the status code of HTTP Request we have info (from ServiceWorker). Can Request be aware somehow that the Response came from ServiceWorker? Comparing date from Response Headers maybe?
By design, a response returned via a FetchEvent#respondWith() is meant to be indistinguishable from a response that had no service worker involvement. This applies regardless of whether the response we're talking about is obtained via XMLHttpRequest, window.fetch(), or setting the src= attribute on some element.
If it's important to you to distinguish which responses originated via service worker involvement, the cleanest way I could think of would be to explicitly add an HTTP header to the Response object that is fed into FetchEvent#respondWith(). You can then check for that header from the controlled page.
However, depending on how your service worker is obtaining its Response, that might be kind of tricky/hacky, and I can't say that I recommend it unless you have a strong use case. Here's what an (again, not recommending) approach might look like:
event.respondWith(
return fetch(event.request).then(function(response) {
if (response.type === 'opaque') {
return response;
}
var headersCopy = new Headers(response.headers);
headersCopy.set('X-Service-Worker', 'true');
return response.arrayBuffer().then(function(buffer) {
return new Response(buffer, {
status: response.status,
statusText: response.statusText,
headers: headersCopy
});
});
})
)
If you get back an opaque Response, you can't do much with it other than return it directly to the page. Otherwise, it will copy a bunch of things over into a new Response that has a an X-Service-Worker header set to true. (This is a roundabout way of working around the fact that you can't directly modify the headers of the Response returned by fetch().)

Categories

Resources