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.

Sponsors