fetch()
lets you make network requests similar to XMLHttpRequest (XHR). The
main difference is that the Fetch API uses Promises, which has a
simpler API to help you avoid the complicated callbacks in the
XMLHttpRequest API.
If you've never used Promises before, check out Introduction to JavaScript Promises.
Basic Fetch Request
Here's an example implemented with an XMLHttpRequest
and then with fetch
. We want to request a URL, get a response, and parse
it as JSON.
XMLHttpRequest
An XMLHttpRequest
needs two listeners to handle the success
and error cases, and a call to open()
and send()
.
Example from MDN docs.
function reqListener () {
const data = JSON.parse(this.responseText);
console.log(data);
}
function reqError (err) {
console.log('Fetch Error :-S', err);
}
const oReq = new XMLHttpRequest();
oReq.onload = reqListener;
oReq.onerror = reqError;
oReq.open('get', './api/some.json', true);
oReq.send();
Fetch
Our fetch request looks like this:
fetch('./api/some.json')
.then(response => {
if (response.status !== 200) {
console.log(`Looks like there was a problem. Status Code: ${response.status}`);
return;
}
// Examine the text in the response
response.json().then(function(data) {
console.log(data);
});
})
.catch(err => {
console.log('Fetch Error :-S', err);
});
The fetch()
request needs only one call to do the same work as the XHR
example. To process the response, we first check that the response status is
200, then parse the response as JSON. The response to a fetch()
request is a
Stream object, which means that after
we call the json()
method, a Promise is returned.
The stream occurs asynchronously.
Response Metadata
The previous example showed the status of the Response object, and how to parse the response as JSON. Here's how to handle other metadata you might want to access, like headers:
fetch('users.json').then(response => {
console.log(response.headers.get('Content-Type'));
console.log(response.headers.get('Date'));
console.log(response.status);
console.log(response.statusText);
console.log(response.type);
console.log(response.url);
});
Response Types
When we make a fetch request, the response will be given a response.type
of "basic
",
"cors
" or
"opaque
".
These types
show where the resource has come from, and you can use them to
determine how to treat the response object.
When the browser requests a resource on the same origin, the response has a
basic
type with restrictions on what you can view from the response.
If a request is made for a resource on another origin, and that origin returns
CORs headers, then the type is cors
. cors
responses are similar to basic
responses, but they restrict the headers you
can view to Cache-Control
, Content-Language
, Content-Type
, Expires
,
Last-Modified
, and Pragma
.
opaque
responses come from a different origin that doesn't return CORS
headers. With an opaque response we won't be able to
read the data returned or view the status of the request, meaning you can't check
whether the request was successful.
You can define a mode for a fetch request so that only certain request types resolve. The modes you can set are as follows:
same-origin
only succeeds for requests for assets on the same origin, and rejects all other requests.cors
allows requests for assets on the same origin and other origins that return the appropriate CORs headers.cors-with-forced-preflight
performs a preflight check before making any request.no-cors
is intended to make requests to other origins that don't have CORS headers and result in anopaque
response, but as stated , this isn't possible in the window global scope at the moment.
To define the mode, add an options object as the second parameter in the
fetch
request and define the mode in that object:
fetch('http://some-site.com/cors-enabled/some.json', {mode: 'cors'})
.then(response => response.text())
.then(text => {
console.log('Request successful', text);
})
.catch(error => {
log('Request failed', error)
});
Promise chaining
One of the great features of promises is the ability to chain them together. For
fetch()
, this lets you share logic across fetch requests.
If you're working with a JSON API, you need to check the status and parse the JSON for each response. You can simplify your code by defining the status and JSON parsing in separate functions that return promises, and use the fetch request to handle only the final data and the error case.
function status (response) {
if (response.status >= 200 && response.status < 300) {
return Promise.resolve(response)
} else {
return Promise.reject(new Error(response.statusText))
}
}
function json (response) {
return response.json()
}
fetch('users.json')
.then(status)
.then(json)
.then(data => {
console.log('Request succeeded with JSON response', data);
}).catch(error => {
console.log('Request failed', error);
});
This example defines a status
function that checks the response.status
and
returns either a resolved Promise as
Promise.resolve()
,
or a rejected Promise as
Promise.reject()
.
This is the first method called in the fetch()
chain.
If the Promise resolves, the script then calls the json()
method, which
returns a second Promise from the response.json()
call and creates an
object containing the parsed JSON. If the parsing fails, the Promise is
rejected and the catch statement executes.
This structure lets you share the logic across all your fetch requests, making code easier to maintain, read and test.
POST Request
Sometimes a web app needs to call an API with a POST method and include some
parameters in the body of the request. To do this, set the method
and body
parameters in the fetch()
options:
fetch(url, {
method: 'post',
headers: {
"Content-type": "application/x-www-form-urlencoded; charset=UTF-8"
},
body: 'foo=bar&lorem=ipsum'
})
.then(json)
.then(data => {
console.log('Request succeeded with JSON response', data);
})
.catch(error => {
console.log('Request failed', error);
});
Send credentials with a fetch request
To make a fetch request with credentials such as cookies, set the request's
credentials
value to "include"
:
fetch(url, {
credentials: 'include'
})