For some handy snippets, check out the "Network stuff" section of my JS Snippets page.
Fetch
Fetch is a newer browser/web API, that is promise
based, and can replace the use of the XMLHttpRequest
api.
Fetch Resources
What & Link | Type |
---|---|
MDN Docs | Doc |
MDN Guide ("Using Fetch") | Guide |
Fetch Usage
Signature:
/**
* @returns {Promise} that resolves to `Response` object
*/
fetch(resource, options);
Simple demo (from MDN):
fetch('http://example.com/movies.json')
.then(function(response) {
return response.json();
})
.then(function(myJsonObj) {
console.log(JSON.stringify(myJsonObj));
});
// Async version
const res = await fetch('http://example.com/movies.json');
const jsonObj = await res.json();
console.log(jsonObj);
What about advanced usage? Options?
Options should be passed as second parameter. Full list is in detail here, and also listed below:
___ | ___ | ___ | ___ |
---|---|---|---|
method | headers | body | mode |
credentials | cache | redirect | referrer |
referrerPolicy | integrity | keepalive | signal |
Response members/methods:
Type | Key |
---|---|
Property | headers |
Property | ok |
Property | redirected |
Property | status |
Property | statusText |
Property | type |
Property | url |
Property | useFinalUrl |
Property | body |
Property | bodyUsed |
Method | clone() |
Method | error() |
Method | redirect() |
Method | arrayBuffer() |
Method | blob() |
Method | formData() |
Method | json() |
Method | text() |
Fetch - GET with Query String Parameters
Sadly, fetch does not have built-in support for passing GET parameters as a raw JS object. However, thanks to the URL()
Web API and URLSearchParams
API, constructing a URL string with query params / querystring is pretty easy:
const params = {
userId: 2,
referrer: 'cheatsheets'
};
const endpoint = 'https://jsonplaceholder.typicode.com/posts';
// Use URL() instance to append query string automatically & format
const url = new URL(endpoint);
url.search = new URLSearchParams(params);
// Notice how we just use URL directly with fetch, but it is NOT a string; it is a URL instance. Fetch will *automatically* call `.toString()` on non-string first arguments
// Better practice might be to explicitly use `url.toString()`
const jsonObj = (await (await fetch(url)).json());
console.log(jsonObj);
Note: Using the
URL()
API has a lot of benefits, aside from easier construction. It also handles a lot of issues around encoding / escaping, so you don't even have to think about that when passing stuff in.
Fetch - Sending POST body
Use FormData
:
var formData = new FormData();
formData.append('email', 'foo@example.com');
fetch('https://example.com/checkEmail', {
method: 'POST',
body: formData
});
The FormData
constructor can also take an HTML <form>
element in the constructor, to grab the data out of, but you can't pass in a raw object. It is pretty easy to write a helper function to fill the data though:
const objToFormData = (obj) => {
const formData = new FormData();
Object.keys(obj).forEach(key => formData.append(key, obj[key]));
return formData;
}
Or, if sending as JSON payload, make sure it is stringified!
fetch('https://example.com/checkEmail', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: 'foo@example.com'
})
});
Fetch - Using Headers
There are multiple ways to send custom headers with a fetch request.
Simple object key-pairs
fetch('https://example.com/postJson', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: myJsonPayload
});
Explicit constructor
const myHeaders = new Headers();
myHeaders.append('Content-Type', 'application/json');
fetch('https://example.com/postJson', {
method: 'POST',
headers: myHeaders,
body: myJsonPayload
});
Or, pass object directly to constructor:
const myHeaders = new Headers({
'Content-Type': 'application/json'
});
fetch('https://example.com/postJson', {
method: 'POST',
headers: myHeaders,
body: myJsonPayload
});
Why use the Headers() interface? There are few benefits; reusability, handy methods, validity checker, etc. Read more here.
Fetch - CORS
As expected, the fetch()
API is subject to the same CORS restrictions that the other browser APIs are. Mainly that if you try to make a cross-origin request, and the response does not have the proper CORS headers, the request will fail.
However, fetch has a specific option for when you want to make a CORS request but don't care about the response. An example of when this might be useful is pinging a server (e.g. with POST
) or sending a tracking hit; you might not need to know what the server responds with. This option is setting mode
to no-cors
, which is also known as using an opaque
response.
The response is opaque
, because you cannot read its contents.
fetch('http://google.com',{
mode: 'no-cors'
}).then(function(response){
console.log(response.type);
// logs > "opaque"
return response.text();
}).then(function(t){
console.log("text = " + t);
// logs > "text = "
});
Warning: "no-cors" has no impact on CORB and can still be blocked by it.
Fetch - Credentials
Like jQuery's withCredentials
option, fetch has init options for passing cookies / credentials. See docs for details, but basically the credentials
param in the init config object needs to be a value from an enum - one of these options:
omit
same-origin
include
There are also experimental plans to allow for more advanced types of credentials; again, refer to the docs for details.
XMLHttpRequest
@TODO
XMLHttpRequest - Resources
What & Link | Type --- | --- | --- MDN Docs | Doc MDN Guide | Guide
JSONP Cross-Origin / Cross-Domain Workaround
JSONP (JSON with Padding) is now considered a legacy approach to working around cross-origin issues; CORs and mechanisms that support it are the preferred current solution. JSONP still has some value though, as some sites still refuse to return the correct headers to support CORs, but accept JSONP responses.
How Does JSONP Work?
JSONP, at its core, is very simple and usually is comprised of two major parts.
- The main concept is that instead of fetching JSON data via
XMLHttp
orfetch()
, you use a<script src="..."></script>
tag. - Since loading via
script
tag can lead to an undetermined state ("is my data there yet or not?"), the second part of JSONP is telling the responding server to call a specific callback function in the window scope. This is also necessary when the response would normally be a pure JSON object, since those don't become part of the global scope anyways.- This is usually done by query string. E.g.:
<script src="//example.com/data/?callback=myCallback"></script>
- When the server responds, it calls your callback with the data payload:
myCallback(dataObj)
. Padding the JSON with the callback code is what makes the P (Padding) part of JSONP.
- This is usually done by query string. E.g.:
JSONP - Pseudo Code Example
Client:
<!-- Define Callback -->
<script>
window.myCallback = (data) => {
console.log('JSONP response received', data);
}
</script>
<script src="//example.com/data/?callback=myCallback"></script>
example.com/data
Server Endpoint (Pseudo Code):
app.get('/data', (req, res) => {
const callbackName = req.query.callback;
const data = {color: 'red'};
// Wrap in callback padding: `callback(data)`
res.send(`${callbackName}(${JSON.stringify(data)})`);
// Or, use shortcut:
// res.jsonp(data);
});
JSONP - Important Reminders
There are a few important things to remember with JSONP:
- It is up to the responding server to implement JSONP on their end.
- Just because you call a JSON endpoint with a
callback=
query parameter doesn't mean that you will get back a JSONP response. It is not the browser that implements JSONP, it is the server!
- Just because you call a JSON endpoint with a
- JSONP has some major security concerns, another reason to prefer newer alternatives
JSONP - Example Client-Side Code
const fetchViaJsonp = (url, callback) => {
const callbackName = 'jsonp_callback_' + Math.round(100000 * Math.random());
window[callbackName] = function (data) {
delete window[callbackName];
document.body.removeChild(script);
callback(data);
};
const script = document.createElement('script');
script.src = url + (url.indexOf('?') >= 0 ? '&' : '?') + 'callback=' + callbackName;
document.body.appendChild(script);
};
You could easily adapt the above code to return a
Promise
, to be similar tofetch
.