Whenever we send or retrieve information with JavaScript, we initiate a thing known as an Ajax call. Ajax is a technique to send and retrieve information behind the scenes without needing to refresh the page. It allows browsers to send and retrieve information, then does things with what it gets back, like add or change HTML on the page.
We all remember the dreaded XMLHttpRequest
we used back in the day to make requests; it involved some messy code, it didn't give us promises and let's be honest, it wasn't pretty JavaScript, right? Maybe if you were using it behind some library.
The Fetch API provides an interface for fetching resources (including across the network). It will seem familiar to anyone who has used XMLHttpRequest
, but the new API provides a more powerful and flexible feature set.
fetch
with XMLHttpRequest
Fire up a code editor, and create an index.html
file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Sparky! Go Fetch!</title>
</head>
<body>
<script>
// Fetch code will go here
</script>
</body>
</html>
http-server
is a simple, zero-configuration command-line http server. It is powerful enough for production usage, but it's simple and hackable enough to be used for testing, local development, and learning.
$ npm install -g http-server
To run it type http-server
from the folder where you created the index.html
file.
An XMLHttpRequest
needs two listeners to be set to handle the success and error cases and a call to open()
and send()
const xmlHttpRequest = new XMLHttpRequest();
xmlHttpRequest.open('GET', 'https://httpbin.org/get');
xmlHttpRequest.addEventListener('load', event => {
const xhr = event.target;
if (xhr.status === 200){
console.log(JSON.parse(xhr.responseText));
} else {
console.log(xhr.status);
}
});
xmlHttpRequest.send();
The fetch request looks like this:
fetch('https://httpbin.org/get')
.then(response => {
if (response.ok) {
return response.json();
}
return Promise.reject(response.status);
})
.then(data => console.log(data))
.catch(error => console.log(error));
We start by checking the very convenient ok
read-only property of the Response
interface. The property contains a Boolean stating whether the response was successful (status in range 200-299) or not.
The response of a fetch()
request is a Stream object, which means that when we call the json()
method, a Promise is returned since the reading of the stream will happen asynchronously.
The Response Object returned by a fetch()
call contains all the information about the request and the response of the network request.
Accessing the headers
property on the response
object gives you the ability to look into the HTTP headers returned by the request:
fetch('https://httpbin.org/get').then(response => {
console.log(response.headers.get('Content-Type');
console.log(response.headers.get('Date');
});
This property is an integer number representing the HTTP response status.
fetch('https://httpbin.org/get').then(response => console.log(response.status));
statusText
is a property representing the status message of the response. If the request is successful, the status is OK
.
fetch('https://httpbin.org/get').then(response => console.log(response.statusText));
url
represents the full URL that we fetched.
fetch('https://httpbin.org/get').then(response => console.log(response.url));
A response had a body, accessible using the text()
or json()
methods, which return a promise.
fetch('https://httpbin.org/get')
.then(response => response.text())
.then(body => console.log(body));
fetch('https://httpbin.org/get')
.then(response => response.json())
.then(body => console.log(body));
The Request object represents a resource request, and it’s usually created using the new Request()
API.
Example:
const request = new Request('/api/todos')
The Request object offers several read-only properties to inspect the resource request details, including
method
: the request's method (GET, POST, etc.)url
: the URL of the requestheaders
: the associated Headers object of the requestreferrer
: the referrer of the requestcache
: the cache mode of the request (e.g., default, reload, no-cache)And exposes several methods including json()
, text()
and formatData()
to process the body of the request.
The full API can be found on MDN.
Being able to set the HTTP request header is essential, and fetch
gives us the ability to do this using the Headers object
const headers = new Headers();
headers.append('Content-Type', 'application/json');
or more simply
const headers = new Headers({
'Content-Type': 'application/json'
});
To attach the headers to the request, we use the Request object, and pass it to fetch()
instead of simply passing the URL.
Instead of:
fetch('https://httpbin.org/get');
we do
cont request = new Request('https://httpbin.org/get', {
headers: new Headers({
'Content-Type': 'application/json'
})
});
fetch(request);
The Headers object is not limited to setting a value, but we can also query it:
header.has('Content-Type');
header.get('Content-Type');
and we can delete a header that was previously set:
headers.delete('X-Custom-Header');
Fetch also allows the usage of any other HTTP method in the request: POST, PUT, DELETE or OPTIONS
Specify the method in the method property of the request, and pass additional parameters into the header and in the request body.
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
brand: 'Apple',
model: 'iPhone Xs',
color: 'Space Gray'
})
};
fetch('https://httpbin.org/anything', options)
.then(response => {
if (response.ok) {
return response.json();
}
return Promise.reject(response.status);
})
.then(data => console.log(data))
.catch(error => console.log(error));
When we make a fetch request, the response will be given a response.type
of basic
, cors
or opaque
. These types
indicate where the resource has come from and can be used to inform us how we should treat the response object.
When a request is made for a resource on the same origin, the response will have a basic
type, and there aren't any restrictions on what you can view from the response.
If a request is made for a resource on another origin which returns the CORS headers
, then the type is cors
. A cors
response restricts the headers you can view to `Cache-Control`, `Content-Language`, `Content-Type`, `Expires`, `Last-Modified`, and `Pragma`.
An opaque
response is for a request made for a resource on 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 we can't check if the request was successful or not.
You can define a mode for a fetch request such that only certain requests will resolve:
same-origin
only succeeds for requests for assets on the same origin; all other requests will reject.cors
will allow requests for assets on the same-origin and other origins which return the appropriate CORS headers.cors-with-forced-preflight
will always perform a preflight check before making the actual requestno-cors
is intended to make requests to other origins that do not have CORS headers and result in an opaque
response, but 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('https://httpbin.org/anything', {mode: 'cors'}})
For a few years after fetch
was introduced, there was no way to abort a request once opened.
Now we can, thanks to the introduction of AbortController
and AbortSignal
, a generic API to notify abort events.
You integrate this API by passing a signal as a fetch parameter
const controller = new AbortController();
const signal = controller.signal;
fetch('https://httpbin.org/anything', { signal });
You can set a timeout that fires an abort event 5 seconds after the fetch request has started, to cancel it:
setTimeout(()=> controller.abort(), 5 * 1000);
Conveniently, it the fetch already returned, calling abort()
won't cause any error.
When an abort signal occurs, fetch will reject the promise with a DOMException
named AbortError
:
const controller = new AbortController();
const signal = controller.signal;
fetch('https://httpbin.org/anything', { signal })
.then(response => response.json())
.then(data => console.log(data))
.catch(error => {
if (error.name === 'AbortError') {
console.error('Fetch aborted');
} else {
console.error('Another error', err);
}
});
Now you have a basic understanding of how to retrieve or manipulate a resource from the server using JavaScript’s Fetch API, as well as how to deal with promises.
Here is a checklist which breaks down the things we learned in this code lab.
Source code for this code lab can be found at https://github.com/The-Guide/fe-guild-pwa-fetch