Guide to using the Fetch API in JavaScript
The Fetch API is the modern way to send and receive data in JavaScript.
The syntax for fetching some data is simple:
fetch('http://endpoint-url.com/api')
This immediately returns a promise that resolves to a Response
object. This provides you with information about the status of the response and headers the data is read.
fetch('http://endpoint-url.com/api')
.then((res) => {
console.log(res.status); // 200
console.log(res.statusText); // OK
console.log(res.headers);
// Headers { "content-length" → "456", "content-type" → "application/json" }
})
The data can be read from the Response
object via a ReadableStream
on its body
property. This also returns a promise.
You can read it by calling a method on the Response
object. The most common is res.json()
to read data that is in JSON format to a JS object:
fetch('http://endpoint-url.com/api')
.then(res => res.json()) // Wait for Response object and read to JS object
.then(data => console.log(data)) // Wait for ReadableStream to be read
.catch(err => console.error(err)) // Runs if error occurs in previous steps
It’s also possible to read the data into a file container using res.blob()
or string format with res.text()
.
An error is not throw automaticaly if you receive an non-success response code to your request. To add this, you can query the statusText
and status
(response code) properties on the Response
object:
fetch('http://endpoint-url.com/api')
.then(res => {
if (!res.statusText === "OK") {
throw new Error(`Bad server response (${res.status})`);
};
return res.json();
})
.then(data => console.log(data))
.catch(err => console.log(err)); // Now runs if there is a bad response code
Sending data
You can make a POST request with Fetch by passing in a configuration object as a second argument when calling it.
Setting a value for a method
property determines the type of request and the data to be sent is set on body
:
const obj = { a: "1", b: "2", c: "3" };
const payload = JSON.stringify(obj);
fetch('http://endpoint-url.com/api', {
method: "POST",
body: payload,
headers: {
"Content-type": "application/json",
}
})
.then(res => res.json())
.then(data => console.log(data))
If sending a form data payload created using the FormData()
constructor, you do not need to set any headers as these will be set automatically. Doing so can cause an error.
With async/await
Because fetch()
returns a promise (and so does reading the data from its Response
object) its result can be handled using async/await syntax, using the await keyword to await
the results of the promises.
Errors can be handled in a catch block that runs if the fetch request, made in a try block, fails:
async function getData(url) {
try {
const res = await fetch(url); // Wait for Repsonse object
const data = await res.json(); // Wait for parsing of data
console.log(data);
} catch(err) {
console.error(err); // Runs if there is an error in try block
}
}
getData('http://endpoint-url.com/api');
Download progress
It is possible to track the download progress of a fetch request, though a little long-winded.
To do so, call getReader()
on the body of the response to generate a new reader. Download progress can be obtained via calling read()
on it and accessing its result, which is in an object returned in a promise every time a chunk of data is read.
At the same time, the data being read needs to be returned in a promise to the next .then()
method. Therefore, this entire process is nested in the creation of a new Response object with a ReadableStream on it.
fetch('https://picsum.photos/400/400')
.then(res => {
const contentLength = res.headers.get('content-length');
let loaded = 0;
// Create new Response object with ReadableStream:
return new Response(
new ReadableStream({
start(controller) {
// At the same time read body of original response with progress:
const reader = res.body.getReader();
(function read() {
reader.read()
.then((progressEvent) => {
if (progressEvent.done) {
controller.close();
return;
}
loaded += progressEvent.value.byteLength;
console.log(Math.round(loaded/contentLength*100)+'%');
// Push new read value into new ReadableStream:
controller.enqueue(progressEvent.value);
read();
})
})();
}
})
);
})
.then(res => res.blob()) // Reads new ReadableStream
.then(blob => {
const url = URL.createObjectURL(blob); // Creates URL to blob
const img = new Image();
img.src = url; // Sets as src of new image
document.body.appendChild(img); // Inserts image in DOM
});