If you’re looking for a gentle introduction to using the Fetch API, a modern replacement for XMLHttpRequest-based Ajax-driven pages, this is the place to start!
Today it’s safe to say that most web apps (including PWAs) make requests for resources on-the-fly, or dynamically. This is often referred to as asynchronous resource loading. This is different from resources that load when the page initially loads. Asynchronous resources are requested on demand in specific circumstances and don’t require a full page load.
As mentioned, in most cases, this type of loading has been done via Ajax, which utilizes a technology called XMLHttpRequest
to make asynchronous resource calls. In this tutorial, I’m going to go through how to use the Fetch API (official), which now has excellent browser support and is slowly starting to replace XMLHttpRequest.
Basic syntax for a Fetch API request
To get started, let’s look at a simple Fetch API example so you can start to get familiar with the basic syntax:
fetch(url)
.then((response) => {
return response.text();
})
.then((data) => {
// do something with 'data'
});
Code language: JavaScript (javascript)
The first line in that code uses the fetch()
method, which is a method of the Window
object in a browser environment. So I could write the first line like this instead:
window.fetch(url)
Code language: JavaScript (javascript)
Fetch can also be used in other environments, but for the purposes of this Fetch API tutorial, I’m going to focus on using the Fetch API in the browser.
The fetch()
method takes two arguments, but only one is mandatory: the location of the requested resource. In the example above, I’m assuming somewhere else in my code I’ve defined that resource’s location in a variable called url
.
Below is a CodePen demo that uses the Fetch API interactively to request an external file and display its contents on the page:
Use the button in the demo to display the content.
Notice the resource that I’m loading in the CodePen demo:
fetch('https://codepen.io/impressivewebs/pen/KKVopdL.html')
Code language: JavaScript (javascript)
That’s a separate CodePen file that I created. CodePen allows you to append .html, .css, or .js to any CodePen URL to request that pen’s HTML, CSS, or JavaScript content, which is useful when using Ajax or Fetch API requests so you’re not hampered by cross-origin limitations. So that’s pretty useful! Try changing the URL to request the CSS or JavaScript (i.e. using KKVopdL.css or KKVopdL.js at the end of the URL) to see the results.
Now that you’ve seen a basic Fetch API example, I’ll introduce you to the different parts of that request.
The resource location parameter
As already discussed, the only argument that’s required when using the fetch()
method is the resource. This will usually be a direct URL to the resource being requested, but it can also be a Request object. All the examples I’m going to use in this tutorial will incorporate a direct URL reference, which is the most common way you’ll see fetch()
used.
Let’s look at some examples using a direct URL to various free APIs as the requested resources (i.e. the resources I want to ‘fetch’).
Here’s a request for some basketball data:
fetch('https://www.balldontlie.io/api/v1/teams/28')
Code language: JavaScript (javascript)
Here’s a request for JavaScript jobs in the New York area:
fetch('https://jobs.github.com/positions.json?description=javascript&location=new+york')
Code language: JavaScript (javascript)
And here’s a request for a random beer:
fetch('https://api.punkapi.com/v2/beers/random')
Code language: JavaScript (javascript)
Any of those URLs can be viewed directly in your browser and they all involve resources from various free APIs. You can try out other free APIs by visiting this GitHub repo. Just make sure you choose one that doesn’t require authentication (i.e. it has “No” under the “Auth” column).
The returned Promises in a Fetch API request
Every fetch()
method begins the process of requesting a resource and returns a Promise. A detailed discussion of JavaScript Promises is out of the scope of this tutorial, but I’ll cover enough for you to be able to work with fetch()
.
According to MDN, a Promise is an object that:
[…] represents the eventual completion (or failure) of an asynchronous operation, and its resulting value.
If I were to convert a Promise into a couple of English sentences, it might look like this:
Do something. When that “something” is finished, do something else.
Of course, that’s an oversimplification, but the concept is more or less there. You’re basically ‘promising’ something then you’re responding when that ‘something’ is completed. And because this is done asynchronously, you can run other lines of JavaScript while the Promise is in the process of being fulfilled.
In the code I used earlier, there are two Promises being returned, one for each .then()
method call:
.then((response) => {
return response.text();
})
.then((data) => {
// do something with 'data'
});
Code language: JavaScript (javascript)
These .then()
methods are doing what’s referred to as chaining, which is possible and quite common with Promises. As MDN explains:
If the function passed as handler to
then
returns aPromise
, an equivalentPromise
will be exposed to the subsequentthen
in the method chain.
So to summarize that example code: I’m ‘fetching’ a resource; ‘then’ I’m returning a response; ‘then’ I’m returning the data after reading the response.
The Promise response in a Fetch API
Notice again in the example code the following syntax:
.then((response) => { }
Code language: JavaScript (javascript)
Passed inside the .then()
method is an arrow function. The .then()
method, which is a feature of JavaScript Promises, takes an argument that’s called an onFullfilled
function. This is the arrow function that’s passed as the argument. This arrow function, in turn, is passing in what’s referred to as the Response object.
When the Promise is fulfilled, the Response object is what’s returned, and this is passed into the function from where it can be handled in a number of ways, which I’ll discuss next.
Useful properties & methods of the response object
There are a number of different properties and methods available on the response object once it’s received from the fetch()
request. Here are a few useful ones:
Response.ok
– This property returns a Boolean indicating whether the request was successful.Response.status
– This property returns the status code of the response (e.g.200
for a successful request).Response.url
– This property returns the URL of the request. Normally you’d have this when initially sending the request, but if you’re creating the request dynamically, this might be useful.
View a full list of properties in this MDN article.
There are also some useful methods you’ll want to be familiar with:
Response.clone()
– Creates a clone of theResponse
objectResponse.text()
– Returns the data in the response as text, which is useful when retrieving HTMLResponse.json()
– Returns the data in the response as a valid JSON object, which is useful when retrieving a JSON string.Response.blob()
– Returns the data as a blob, which is useful for data that’s in the form of a file reference (like an image URL).
Again, you can view a full list of valid methods in this MDN article.
Reading the response
You’ll notice in my example that I’m including two chained .then()
methods when dealing with the content being requested. The first .then()
is where I’m dealing with the response object and the second .then()
is where I’m dealing with the data after it’s been read (usually via one of the methods mentioned in the previous section).
Once the response is received and I’ve chosen what to do with it (e.g. read it as text or as a JSON object), then I can pass it to the next function in the next .then()
call. When that subsequent Promise is fulfilled, I’m able to manipulate the data however I want.
This is similar to how Ajax calls work. Whether I’m working with Ajax’s XMLHttpRequest
or the Fetch API, once I’ve received the data in a usable format on the current page, I can do with it what I like.
When is a Fetch API request considered rejected?
An important distinction to make when looking at successful and unsuccessful Fetch API requests is when considering network errors vs. HTTP errors.
When making a fetch()
request, if permission is not granted to the document that initiated the fetch, then this is considered a rejected request. This occurs at the network level. However, if a fetch request is asking for content that doesn’t exist on the server, this is still considered a successful request, so the Promise is viewed as successfully fulfilled.
To illustrate, take a look at the following CodePen:
This is the same code that I used earlier, but this time instead of requesting another CodePen document (which I’m allowed to do on CodePen), I’m attempting to request the CodeinWP homepage. This doesn’t work because I don’t have permission from wpshout.com to make Fetch or Ajax requests from codepen.com. So in this case, the Fetch API request is considered rejected.
Now notice the different error message that appears when viewing the following demo:
Open up the browser console to see the messages displayed. Here’s the JavaScript I’m using for that demo:
fetch('https://www.impressivewebs.com/page-doesnt-exist')
.then((response) => {
console.log('Promise was fulfilled');
return response.status;
})
.then((status) => {
console.log('Promise 2 was fulfilled');
document.querySelector('.par').innerHTML = status;
document.querySelector('.par').classList.add('box');
});
Code language: JavaScript (javascript)
The resource I’m requesting doesn’t exist, but I have access to the server I’m requesting it from since it’s on the same origin. To demonstrate that the Promises are fulfilled, I’m displaying a console message for each one. But you’ll notice the browser console also displays a GET
error message and the Response
object returns a value of 404
for the status
property. Similarly, if I logged Response.ok
I would get a generic value of false
, rather than a specific status message.
Knowing how this works, you can deal with situations where the content isn’t found, for example by providing a custom error message inside the first .then()
method:
.then((response) => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
})
Code language: JavaScript (javascript)
Applying custom settings to a Fetch API request
As mentioned, the only mandatory argument when using fetch()
is the location of the resource, usually a string value in the form of a relative or absolute URL. But fetch()
also allows you to provide a second optional argument, the init
object.
Here’s an example that includes some possible settings inside init
:
fetch(url, {
method: 'GET',
mode: 'no-cors',
cache: 'no-cache',
referrerPolicy: 'same-origin'
})
Code language: CSS (css)
You can see a full list of settings for the optional init
parameter in the Syntax section of the MDN article on fetch()
.
The ones I’ve included above are a few of the more common ones. For example, method
lets me define whether the request is a GET
or POST
(it will usually be a GET
, which is the default). The mode
allows me to define the type of request. I could set this to “no-cors” which would allow me to request a resource from anywhere. But “no-cors” means I’m limited as to what I can do with the content. For example, I can’t access it with JavaScript but it can be used in a Service Worker environment. Finally, I can define a referrer policy and the cache
setting lets me define a cache mode for the requested resource.
These are just a few examples of the many settings available, but not all are going to be useful to you in most examples when using the Fetch API. You’ll likely only work with one or two of these, or you you’ll leave the init
parameter out and work with the defaults.
Requesting JSON data via the Fetch API
As mentioned earlier, there are a number of different services that allow you to request data via their APIs. One such service is called Dog API, that claims to be “the internet’s biggest collection of open source dog pictures”. I’m going to use that API to demonstrate obtaining JSON data via the Fetch API.
Here’s the code:
fetch('https://dog.ceo/api/breeds/image/random')
.then((response) => {
return response.json();
})
.then((myContent) => {
myImage.src = myContent['message'];
});
Code language: JavaScript (javascript)
Notice the first line is the URL of the request. As mentioned on the Dog API documentation, you can use their API to grab a random dog image. The response that’s returned is in JSON format that looks like this:
{
"message": "https://images.dog.ceo/breeds/boxer/IMG_3394.jpg",
"status": "success"
}
Code language: JSON / JSON with Comments (json)
I’m only concered with the URL found in the “message” property, which is why I have the following line in my code:
myImage.src = myContent['message'];
Code language: JavaScript (javascript)
The myContent
variable is passed into the second .then()
method, which happens after the response is converted to JSON format using the .json()
method that I mentioned earlier. As long as I have access to the data in valid JSON format, from there I can do anything I would normally be able to do with JSON in JavaScript.
Here’s a live CodePen demo so you can try it out:
Use the button in the demo to request a random dog image that gets displayed on the page. This uses an event listener to change the image each time the button is clicked.
The Fetch API in older browsers
Browser support shouldn’t be a big problem in most cases, but if you still need to support Fetch in older browsers, there are some workarounds and polyfills.
You can check for the existence of Fetch using something like the following code:
if (!('fetch' in window)) {
// fetch not available, use a polyfill
} else {
// fetch is available
}
Code language: JavaScript (javascript)
And here are a couple of links to Fetch API polyfills:
- unfetch – This is a minimal polyfill that’s under 500 bytes that works in IE8+
- window.fetch polyfill – Implements a subset of the standard Fetch specification
Keep in mind that if you’re bundling your app using something like Parcel.js along with its built-in support for Babel, you’ll have to include a polyfill separately if your Babel settings go back to old version of IE or another browser that doesn’t support the Fetch API.
Conclusion
There’s a lot more that could be covered when it comes to using the Fetch API, and I’m still researching the various features. But this should give you a nice starting point.
I hope the examples in this post have been useful to you to get a decent overall understanding of how the Fetch API works and how it can be useful in dynamic apps in place of the old Ajax syntax.
…
Don’t forget to join our crash course on speeding up your WordPress site. With some simple fixes, you can reduce your loading time by even 50-80%:
Layout and presentation by Karol K.