Introduction to fetch()

Matt Gaunt

fetch() lets you make network requests similar to XMLHttpRequest (XHR). The main difference is that the Fetch API uses Promises, which has a simpler API to help you avoid the complicated callbacks in the XMLHttpRequest API.

Browser Support

  • Chrome: 42.
  • Edge: 14.
  • Firefox: 39.
  • Safari: 10.1.

Source

If you've never used Promises before, check out Introduction to JavaScript Promises.

Here's an example implemented with an XMLHttpRequest and then with fetch. We want to request a URL, get a response, and parse it as JSON.

An XMLHttpRequest needs two listeners to handle the success and error cases, and a call to open() and send(). Example from MDN docs.

function reqListener () {
  const data = JSON.parse(this.responseText);
  console.log(data);
}

function reqError (err) {
  console.log('Fetch Error :-S', err);
}

const oReq = new XMLHttpRequest();
oReq.onload = reqListener;
oReq.onerror = reqError;
oReq.open('get', './api/some.json', true);
oReq.send();

Fetch

Our fetch request looks like this:

fetch('./api/some.json')
  .then(response => {
    if (response.status !== 200) {
      console.log(`Looks like there was a problem. Status Code: ${response.status}`);

      return;
    }

    // Examine the text in the response
    response.json().then(function(data) {
      console.log(data);
    });
  })
  .catch(err => {
    console.log('Fetch Error :-S', err);
  });

The fetch() request needs only one call to do the same work as the XHR example. To process the response, we first check that the response status is 200, then parse the response as JSON. The response to a fetch() request is a Stream object, which means that after we call the json() method, a Promise is returned. The stream occurs asynchronously.

Response Metadata

The previous example showed the status of the Response object, and how to parse the response as JSON. Here's how to handle other metadata you might want to access, like headers:

fetch('users.json').then(response => {
  console.log(response.headers.get('Content-Type'));
  console.log(response.headers.get('Date'));

  console.log(response.status);
  console.log(response.statusText);
  console.log(response.type);
  console.log(response.url);
});

Response Types

When we make a fetch request, the response will be given a response.type of "basic", "cors" or "opaque". These types show where the resource has come from, and you can use them to determine how to treat the response object.

When the browser requests a resource on the same origin, the response has a basic type with restrictions on what you can view from the response.

If a request is made for a resource on another origin, and that origin returns CORs headers, then the type is cors. cors responses are similar to basic responses, but they restrict the headers you can view to Cache-Control, Content-Language, Content-Type, Expires, Last-Modified, and Pragma.

opaque responses come from a different origin that doesn't return CORS headers. With an opaque response we won't be able to read the data returned or view the status of the request, meaning you can't check whether the request was successful.

You can define a mode for a fetch request so that only certain request types resolve. The modes you can set are as follows:

  • same-origin only succeeds for requests for assets on the same origin, and rejects all other requests.
  • cors allows requests for assets on the same origin and other origins that return the appropriate CORs headers.
  • cors-with-forced-preflight performs a preflight check before making any request.
  • no-cors is intended to make requests to other origins that don't have CORS headers and result in an opaque response, but as stated , this isn't possible in the window global scope at the moment.

To define the mode, add an options object as the second parameter in the fetch request and define the mode in that object:

fetch('http://some-site.com/cors-enabled/some.json', {mode: 'cors'})
  .then(response => response.text())
  .then(text => {
    console.log('Request successful', text);
  })
  .catch(error => {
    log('Request failed', error)
  });

Promise chaining

One of the great features of promises is the ability to chain them together. For fetch(), this lets you share logic across fetch requests.

If you're working with a JSON API, you need to check the status and parse the JSON for each response. You can simplify your code by defining the status and JSON parsing in separate functions that return promises, and use the fetch request to handle only the final data and the error case.

function status (response) {
  if (response.status >= 200 && response.status < 300) {
    return Promise.resolve(response)
  } else {
    return Promise.reject(new Error(response.statusText))
  }
}

function json (response) {
  return response.json()
}

fetch('users.json')
  .then(status)
  .then(json)
  .then(data => {
    console.log('Request succeeded with JSON response', data);
  }).catch(error => {
    console.log('Request failed', error);
  });

This example defines a status function that checks the response.status and returns either a resolved Promise as Promise.resolve(), or a rejected Promise as Promise.reject(). This is the first method called in the fetch() chain.

If the Promise resolves, the script then calls the json() method, which returns a second Promise from the response.json() call and creates an object containing the parsed JSON. If the parsing fails, the Promise is rejected and the catch statement executes.

This structure lets you share the logic across all your fetch requests, making code easier to maintain, read and test.

POST Request

Sometimes a web app needs to call an API with a POST method and include some parameters in the body of the request. To do this, set the method and body parameters in the fetch() options:

fetch(url, {
    method: 'post',
    headers: {
      "Content-type": "application/x-www-form-urlencoded; charset=UTF-8"
    },
    body: 'foo=bar&lorem=ipsum'
  })
  .then(json)
  .then(data => {
    console.log('Request succeeded with JSON response', data);
  })
  .catch(error => {
    console.log('Request failed', error);
  });

Send credentials with a fetch request

To make a fetch request with credentials such as cookies, set the request's credentials value to "include":

fetch(url, {
  credentials: 'include'
})