Introduction to the Fetch API

Share this article

Introduction to the Fetch API

In this article, we’ll learn what the new Fetch API looks like, what problems it solves, and the most practical way to retrieve remote data inside your web page using the fetch() function.

For years, XMLHttpRequest has been web developers’ trusted sidekick. Whether directly or under the hood, XMLHttpRequest has enabled Ajax and a whole new type of interactive experience, from Gmail to Facebook.

However, XMLHttpRequest is slowly being superseded by the Fetch API. Both can be used to make network requests, but the Fetch API is Promise-based, which enables a cleaner, more concise syntax and helps keep you out of callback hell.

The Fetch API

The Fetch API provides a fetch() method defined on the window object, which you can use to perform requests. This method returns a Promise that you can use to retrieve the response of the request.

The fetch method only has one mandatory argument, which is the URL of the resource you wish to fetch. A very basic example would look something like the following. This fetches the top five posts from r/javascript on Reddit:

fetch('https://www.reddit.com/r/javascript/top/.json?limit=5')
.then(res => console.log(res));

If you inspect the response in your browser’s console, you should see a Response object with several properties:

{
  body: ReadableStream
  bodyUsed: false
  headers: Headers {}
  ok: true
  redirected: false
  status: 200
  statusText: ""
  type: "cors"
  url: "https://www.reddit.com/top/.json?count=5"
}

It seems that the request was successful, but where are our top five posts? Let’s find out.

Loading JSON

We can’t block the user interface waiting until the request finishes. That’s why fetch() returns a Promise, an object which represents a future result. In the above example, we’re using the then method to wait for the server’s response and log it to the console.

Now let’s see how we can extract the JSON payload from that response once the request completes:

fetch('https://www.reddit.com/r/javascript/top/.json?limit=5')
.then(res => res.json())
.then(json => console.log(json));

We start the request by calling fetch(). When the promise is fulfilled, it returns a Response object, which exposes a json method. Within the first then() we can call this json method to return the response body as JSON.

However, the json method also returns a promise, which means we need to chain on another then(), before the JSON response is logged to the console.

And why does json() return a promise? Because HTTP allows you to stream content to the client chunk by chunk, so even if the browser receives a response from the server, the content body might not all be there yet!

Async … await

The .then() syntax is nice, but a more concise way to process promises in 2018 is using async … await — a new syntax introduced by ES2017. Using async Ú await means that we can mark a function as async, then wait for the promise to complete with the await keyword, and access the result as a normal object. Async functions are supported in all modern browsers (not IE or Opera Mini) and Node.js 7.6+.

Here’s what the above example would look like (slightly expanded) using async … await:

async function fetchTopFive(sub) {
  const URL = `https://www.reddit.com/r/${sub}/top/.json?limit=5`;
  const fetchResult = fetch(URL)
  const response = await fetchResult;
  const jsonData = await response.json();
  console.log(jsonData);
}

fetchTopFive('javascript');

Not much has changed. Apart from the fact that we’ve created an async function, to which we’re passing the name of the subreddit, we’re now awaiting the result of calling fetch(), then using await again to retrieve the JSON from the response.

That’s the basic workflow, but things involving remote services doesn’t always go smoothly.

Handling Errors

Imagine we ask the server for a non-existing resource or a resource that requires authorization. With fetch(), you must handle application-level errors, like 404 responses, inside the normal flow. As we saw previously, fetch() returns a Response object with an ok property. If response.ok is true, the response status code lies within the 200 range:

async function fetchTopFive(sub) {
  const URL = `http://httpstat.us/404`; // Will return a 404
  const fetchResult = fetch(URL)
  const response = await fetchResult;
  if (response.ok) {
    const jsonData = await response.json();
    console.log(jsonData);
  } else {
    throw Error(response.statusText);
  }
}

fetchTopFive('javascript');

The meaning of a response code from the server varies from API to API, and oftentimes checking response.ok might not be enough. For example, some APIs will return a 200 response even if your API key is invalid. Always read the API documentation!

To handle a network failure, use a try … catch block:

async function fetchTopFive(sub) {
  const URL = `https://www.reddit.com/r/${sub}/top/.json?limit=5`;
  try {
    const fetchResult = fetch(URL)
    const response = await fetchResult;
    const jsonData = await response.json();
    console.log(jsonData);
  } catch(e){
    throw Error(e);
  }
}

fetchTopFive('javvascript'); // Notice the incorrect spelling

The code inside the catch block will run only when a network error occurs.

You’ve learned the basics of making requests and reading responses. Now let’s customize the request further.

Change the Request Method and Headers

Looking at the example above, you might be wondering why you couldn’t just use one of the existing XMLHttpRequest wrappers. The reason is that there’s more the fetch API offers you than just the fetch() method.

While you must use the same instance of XMLHttpRequest to perform the request and retrieve the response, the fetch API lets you configure request objects explicitly.

For example, if you need to change how fetch() makes a request (e.g. to configure the request method) you can pass a Request object to the fetch() function. The first argument to the Request constructor is the request URL, and the second argument is an option object that configures the request:

async function fetchTopFive(sub) {
  const URL = `https://www.reddit.com/r/${sub}/top/.json?limit=5`;
  try {
    const fetchResult = fetch(
      new Request(URL, { method: 'GET', cache: 'reload' })
    );
    const response = await fetchResult;
    const jsonData = await response.json();
    console.log(jsonData);
  } catch(e){
    throw Error(e);
  }
}

fetchTopFive('javascript');

Here, we specified the request method and asked it never to cache the response.

You can change the request headers by assigning a Headers object to the request headers field. Here’s how to ask for JSON content only with the 'Accept' header:

const headers = new Headers();
headers.append('Accept', 'application/json');
const request = new Request(URL, { method: 'GET', cache: 'reload', headers: headers });

You can create a new request from an old one to tweak it for a different use case. For example, you can create a POST request from a GET request to the same resource. Here’s an example:

const postReq = new Request(request, { method: 'POST' });

You also can access the response headers, but keep in mind that they’re read-only values.

fetch(request).then(response => console.log(response.headers));

Request and Response follow the HTTP specification closely; you should recognize them if you’ve ever used a server-side language. If you’re interested in finding out more, you can read all about them on the Fetch API page on MDN.

Bringing it all Together

To round off the article, here’s a runnable example demonstrating how to fetch the top five posts from a particular subreddit and display their details in a list.

See the Pen Fetch API Demo by SitePoint (@SitePoint) on CodePen.

Try entering a few subreddits (e.g. ‘javascript’, ‘node’, ‘linux’, ‘lolcats’) as well as a couple of non-existent ones.

Where to Go from Here

In this article, you’ve seen what the new Fetch API looks like and what problems it solves. I’ve demonstrated how to retrieve remote data with the fetch() method, how to handle errors and to create Request objects to control the request method and headers.

And as the following graphic shows, support for fetch() is good. If you need to support older browsers a polyfill is available.

Can I Use fetch? Data on support for the fetch feature across the major browsers from caniuse.com.

So next time you reach for a library like jQuery to make Ajax requests, take a moment to think if you could use native browser methods instead.

Frequently Asked Questions (FAQs) about the Fetch API

What is the difference between Fetch API and XMLHttpRequest?

Fetch API and XMLHttpRequest are both used to make HTTP requests in JavaScript. However, Fetch API is a more modern and powerful approach. It returns a Promise that resolves to the Response to that request, whether it is successful or not. This makes it easier to handle responses and errors. Fetch API also has a cleaner, more intuitive syntax compared to XMLHttpRequest. It supports all modern browsers, but for older ones, you may need to use a polyfill.

How can I handle errors with Fetch API?

Fetch API uses Promises, which makes error handling a bit different compared to traditional callback-based APIs. A Fetch Promise will only reject if there is a network error. If the server returns an error status like 404 or 500, the Promise will still resolve, but the ok property of the Response object will be false. You can check this property and throw an error if needed. Here’s an example:

fetch(url)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.catch(error => console.error('There has been a problem with your fetch operation:', error));

Can I use Fetch API with other HTTP methods like POST, PUT, DELETE?

Yes, Fetch API supports all HTTP methods. You can specify the method in the options object when calling fetch(). Here’s an example of a POST request:

fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
})
.then(response => response.json())
.catch(error => console.error('Error:', error));

How can I send headers with Fetch API?

You can send headers by adding a headers property to the options object when calling fetch(). The headers property should be an object where each key-value pair represents a header name and its value. Here’s an example:

fetch(url, {
headers: {
'Authorization': 'Bearer ' + token,
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.catch(error => console.error('Error:', error));

Can I cancel a fetch request?

Yes, you can cancel a fetch request using the AbortController API. You create an instance of AbortController, pass its signal property to the fetch() call, and call abort() on the controller when you want to cancel the request. Here’s an example:

const controller = new AbortController();
const signal = controller.signal;

fetch(url, { signal })
.then(response => response.json())
.catch(error => {
if (error.name === 'AbortError') {
console.log('Fetch aborted');
} else {
console.error('Error:', error);
}
});

// Call this function to cancel the fetch request
function cancelFetch() {
controller.abort();
}

How can I use Fetch API with async/await?

Fetch API returns Promises, so you can use it with async/await for cleaner, more readable code. Here’s an example:

async function fetchData(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error('Network response was not ok');
}
const data = await response.json();
return data;
} catch (error) {
console.error('There has been a problem with your fetch operation:', error);
}
}

How can I make multiple fetch requests at the same time?

You can use Promise.all() to make multiple fetch requests at the same time. Promise.all() takes an array of Promises and returns a new Promise that resolves when all the input Promises have resolved. Here’s an example:

Promise.all([
fetch(url1),
fetch(url2),
fetch(url3)
])
.then(responses => Promise.all(responses.map(response => response.json())))
.then(data => console.log(data))
.catch(error => console.error('Error:', error));

How can I use Fetch API with cookies?

By default, Fetch API doesn’t send or receive any cookies. If you want to include cookies in the request, you need to set the credentials option to ‘include’. Here’s an example:

fetch(url, {
credentials: 'include'
})
.then(response => response.json())
.catch(error => console.error('Error:', error));

How can I use Fetch API with CORS?

Fetch API follows the same-origin policy, which means by default it can only make requests to the same origin as the current page. If you want to make a cross-origin request, you need to set the mode option to ‘cors’. Note that the server also needs to send the appropriate CORS headers. Here’s an example:

fetch(url, {
mode: 'cors'
})
.then(response => response.json())
.catch(error => console.error('Error:', error));

How can I use Fetch API with JSON data?

Fetch API makes it easy to work with JSON data. You can use the json() method of the Response object to parse the response body as JSON. Here’s an example:

fetch(url)
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));

Ludovico FischerLudovico Fischer
View Author

Full-stack developer working with Scala and JavaScript.

ajaxajax getajax postapiAPIsAurelioDFetch APIlearn-modernjsmodernjsmodernjs-hubXMLHttpRequest
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week