Fetching Data with the Fetch API
In this module we’ll explore using the Fetch API to download data directly from an URL or an API.
Table of Contents
- But You Promised!
- And Then What?
- The Fetch API
- Fetching JSON
- GET Request
- POST Requests
- POST with JSON
- Handling Errors
- Then Chains
- Async / Await
- Further Reading
But You Promised!
⏳ Wait For It:
Before we can talk about fetching data, a word on Promises.
Promises are a recent addition to Javascript that represent the eventual result of an asynchronous operation.
You ask for something and you get back a promise for that thing.
A promise can be in one of four states:
- fulfilled - Result is available.
- rejected - Failed to retrieve result.
- pending - Not yet fulfilled or rejected.
- settled - Fulfilled or rejected.
And Then What?
We use .then()
to register a callback to handle fulfilled promises. This callback will be called once the promise has been fulfilled.
We can chain .then()
s together to link together a series of async tasks.
We can also register a callback to handle rejected promises using .catch()
.
All of this will make more sense with an example. So let’s dive into fetching data using Javascript’s Fetch API.
The Fetch API
The Fetch API makes available a global fetch()
function that we can use to request data from a URL using HTTP. This function returns a Promise
we can use to eventually access the fetched data.
fetch("https://dog.ceo/api/breeds/list/all").then(function (result) {
console.log("HTTP Response Status: " + result.status);
});
This request will print to the console once the Promise
is fulfilled:
HTTP Response Status: 200
Fetching JSON
If the data being fetch
ed is JSON we can request another Promise
for the de-serialized Javascript version of the JSON string.
fetch("https://dog.ceo/api/breeds/list/all")
.then(function (result) {
return result.json(); // Promise for parsed JSON.
})
.then(function (data) {
// Executed when promised JSON is ready.
let breeds = Object.keys(data.message);
for (let breed of breeds) {
console.log(breed);
}
});
The reasons we set breeds
to Object.keys(data.message)
is because the API returns:
{
"status": "success",
"message": {
"affenpinscher": [],
// skip a few
"setter": [
"english",
"gordon",
"irish"
],
// skip a few more
"wolfhound": [
"irish"
]
}
}
And for this example we only care about the keys of the message
property.
GET Request
Here’s another example of an HTTP GET request for some JSON:
fetch("https://www.reddit.com/r/javascript/top/.json?limit=5")
.then(function (result) {
return result.json(); // Promise for parsed JSON.
})
.then(function (retrieved) {
let articles = retrieved.data.children;
for (let article of articles) {
console.log(article.data.title);
}
});
The relevant keys of the JSON returned by the Reddit API look like this:
{
"data": {
"children": [
{
"data": {
"title": "Title of the first article"
}
},
{
"data": {
"title": "Title of the second article"
}
}
// etc
]
}
}
POST Requests
If you wanted to use Javascript to asynchronously submit an HTML form:
fetch("https://example.com/endpoint", {
method: "post",
body: new FormData(document.querySelector("form")),
}).then(function (response) {
if (!response.ok) {
console.log("POST failed. Status Code: " + response.status);
}
});
This will submit the data present in the first form on the page.
POST with JSON
Some APIs will have endpoints that you can POST JSON data to:
fetch("https://jsonplaceholder.typicode.com/posts", {
method: "POST",
body: JSON.stringify({ title: "foo", body: "bar", userId: 1 }),
headers: {
"Content-type": "application/json; charset=UTF-8",
},
})
.then((response) => response.json())
.then((json) => console.log(json));
Note the use of the fat arrow functions in the then
s.
Handling Errors
Errors can be handled by chaining a .catch
to your fetch
, but this will only catch network errors. It won’t catch bad HTTP status responses like 404 or 500.
To handle cases where the network request succeeded but the HTTP status code was bad:
function handleErrors(response) {
if (!response.ok) {
throw Error(response.statusText);
}
return response;
}
fetch("http://httpstat.us/500")
.then(handleErrors) // Check for HTTP status failure.
.then(function(response) { // Handle success. })
.catch(function(error) { // Handle network or HTTP failure. })
Then Chains
When working with promises we sometimes end up with a chain of .then()
calls to resolve multiple nested asynchronous callbacks. These can be hard to read and reason about.
The dog breed API example from above generates two promises, one for the fetch
and another for parsing the returned JSON string:
fetch("https://dog.ceo/api/breeds/list/all") // Fetch returns a promise.
.then(function (result) {
// Then for promised fetch result.
return result.json(); // JSON parsing also returns a promise.
})
.then(function (data) {
// Then for promised JSON parsing.
let breeds = Object.keys(data.message);
for (let breed of breeds) {
console.log(breed);
}
});
Async / Await
Let’s move the code from the last example into a logAllBreeds
function. If we mark this function as async
we can use the await
keyword to write code that doesn’t rely on callbacks:
async function logAllBreeds() {
let results = await fetch("https://dog.ceo/api/breeds/list/all");
let data = await results.json();
let breeds = Object.keys(data.message);
for (let breed of breeds) {
console.log(breed);
}
}
The async
keyword ensures that a function always returns a promise.
The await
keyword makes Javascript wait until a promise is settled before the code continues executing.
Further Reading
- More Details on Async / Await @ javascript.info
- Axios - Popular alternative to using Fetch.
- httpstat.us - A simple service for generating different HTTP codes.
- Can I Use Promises? - Current browser support for promises.
- Can I Use the Fetch API - Current browser support for the Fetch API.