Getting Started with JavaScript Fetch API
Prelude
Have you ever used XMLHttpRequest
object in JavaScript to send a request to the backend to fetch or post some data? If not, then lucky you, as it was pretty chaotic and complicated.
Here’s a quick example of what it takes to fetch a file from a given URL using XMLHttpRequest
object vs $.ajax()
in jQuery vs newly introduced native fetch
API.
Fetching Data using XMLHttpRequest
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState == XMLHttpRequest.DONE) {
console.log(xhr.responseText);
}
}
xhr.open('GET', 'https://api.jsonbin.io/v3/b/6360e54a65b57a31e6a94a42', true);
xhr.send(null);
Looks tedious right? That’s not it, the above is mostly a minimal example of how to send a request using XMLHttpRequest()
object. Next, jQuery comes to the rescue by introducing $.ajax()
, a simplified version of the XMLHttpRequest()
as a cherry on top, introducing additional functionalities.
Fetching Data using $.get()
$.get(
'https://api.jsonbin.io/v3/b/6360e54a65b57a31e6a94a42',
(res) => {
console.log(res);
}
);
jQuery simplified fetching the data to a great extent, compared to the native XMLHttpRequest()
object. Though it was easy, it came at some cost, as the jQuery lib weighed ~100kb (vague estimation) where even if you built the lib with ajax only module, it still weighed roughly ~50kb which is pretty high to do something as basic as fetching some JSON data.
Before we dive into using fetch()
API, here's something important to take a note of:
The fetch specification differs from
jQuery.ajax()
in the following significant ways:
- The Promise returned from
fetch()
won't reject on HTTP error status even if the response is an HTTP 404 or 500. Instead, as soon as the server responds with headers, the Promise will resolve normally (with the ok property of the response set to false if the response isn't in the range 200–299), and it will only reject on network failure or if anything prevented the request from completing. [1] - Unless
fetch()
is called with thecredentials
option set toinclude
,fetch()
Quoting from MDN: - Won’t send cookies in cross-origin requests.
- Won’t set any cookies sent back in cross-origin responses.
Introducing the fetch() API
fetch
API is supported by most of the modern browsers today. It makes it easier for the devs to make asynchronous network requests as it's promise based, thus, avoids callback hell.
Here’s a simple example of making a GET
request using fetch
API.
fetch('https://api.jsonbin.io/v3/b/6360e54a65b57a31e6a94a42')
.then((res) => res.json())
.then((data) => console.log(data));
You could also use async ... await
instead of promises, like:
(async () => {
const res = await fetch('https://api.jsonbin.io/v3/b/6360e54a65b57a31e6a94a42');
const data = await res.json();
console.log(data);
})();
In the above examples, fetch()
takes one argument which is the path to the resource you wish to fetch. An important thing to note here, the response does not return the JSON response body but it instead a representation of the entire HTTP response where you could use .json()
to fetch the actual response JSON data.
fetch
also provides various options which you could use for the response object, like, text()
which returns raw text, formData()
, blob()
, etc.
Error Handling
When I first tried fetch, I used .catch()
assuming that my code would end up in the .catch()
statement if the request was a non 200. To my surprise, .catch()
never ran, even if the request failed with a response code of 4xx or 5xx. As mentioned previously in this post.
For eg, the following URL would throw a 404 error.
fetch('https://api.jsonbin.io/v3/b/6360e54a65b57a31e6a94a4a')
.then((res) => res.json())
.then((data) => console.log(data))
.catch((err) => console.log('Something went wrong'));
Here, my code didn’t print Something went wrong
. Later, referring to the official documentation, it turns out that fetch doesn't throw an error if the server returns an error (4xx or 5xx).
The only time my code would end up in the catch statement is when there’s no internet connection, or similar issues. Here’s where response.ok
comes to the rescue. Using response.ok
would return true if the response status code is between 200-299
else false.
In the above example, using response.ok
to check the request state would return false
as the response code was 404
.
(async () => {
const res = await fetch('https://api.jsonbin.io/v3/b/6360e54a65b57a31e6a94a42');
if(!res.ok) {
throw new Error(`'Request failed with status code: ${res.status}`);
}
const data = await res.json();
console.log(data);
})();
Attaching Request Headers & Payload
In the above examples, we tried to fetch these records using the default GET
method.
But what if you wish to send a POST
or a PUT
request along with some Headers
attached to it?
You could pass headers
and attach a payload to your fetch
request either by using Headers()
object or you could simply pass these in an object as a second parameter to the fetch
API like:
(async () => {
const res = await fetch('https://api.jsonbin.io/v3/b/6360e54a65b57a31e6a94a42',
{
method: 'POST', // *GET, POST, PUT, DELETE, etc.
mode: 'cors', // no-cors, *cors, same-origin
headers: { // Request headers
'Content-Type': 'application/json'
},
body: JSON.stringify({"sample": "json"}) // Request payload
});
if(!res.ok) {
throw new Error(`'Request failed with status code: ${res.status}`);
}
const data = await res.json();
console.log(data);
})();
fetch()
vs. XHR
There are a few differences between the fetch
API and XHR request. I didn't want to repeat or copy these since I myself got to know about these differences from a Stackoverflow answer.
Browser Support
fetch()
API now has a decent Browser Support. Here's a quick Browser Support overview from MDN:
You could also use fetch
in NodeJS using --experimental-fetch
flag, or use the node-fetch package if needed.