Fetch API - Replacement for XMLHttpRequest (XHR)

The Fetch API allows you to make network requests similar to XMLHttpRequest (XHR). The main difference is that the Fetch API uses Promises, which enables a simpler and cleaner API, avoiding callback hell and having to remember the complex API of XMLHttpRequest.

Let’s start by comparing a simple example implemented with an XMLHttpRequest and then with fetch.

XMLHttpRequest

XHR is a bit overcomplicated in my opinion, and don’t get me started on why “XML” is uppercase but “Http” is camel-cased. Anyways, this is how you use XHR now:

// Just getting XHR is a mess!
if (window.XMLHttpRequest) { // Mozilla, Safari, ...
  request = new XMLHttpRequest();
} else if (window.ActiveXObject) { // IE
  try {
    request = new ActiveXObject('Msxml2.XMLHTTP');
  }
  catch (e) {
    try {
      request = new ActiveXObject('Microsoft.XMLHTTP');
    }
    catch (e) {}
  }
}

function onLoadListener() {
  var data = JSON.parse(this.responseText);
  console.log(data);
}

function onErrorListener(err) {
  console.log('XHR Error :', err);
}

request.onload = onLoadListener;
request.onerror = onErrorListener;
request.open('get', 'https://api.acme.com/some.json', true);
request.send();

Fetch API

For making a request and fetching a resource, use the window.fetch method. The fetch() method takes one mandatory argument, the URL to the resource you want to fetch. It returns a promise that resolves to the Response to that request, whether it is successful or not.

fetch('https://api.acme.com/some.json')
.then(function(response) {
    return response.json();
}).then(function(jsonData) {
    console.log(jsonData);
}).catch(function(err) {
    console.log("Opps, Something went wrong!", err);
})

Let’s say you make a request for JSON – the resulting callback data has a json method(response.json()) for converting the raw data to a JavaScript object.

Chaining Promises

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

If you are working with a JSON API, you’ll 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 which return promises, freeing you to only worry about handling the final data and the error case.

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

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

fetch('https://api.acme.com/users.json')
  .then(checkStatus)
  .then(parseJson)
  .then(function(data) {
    console.log('Request succeeded with JSON response', data);
  }).catch(function(error) {
    console.log('Request failed', error);
  });

We define the checkStatus function which checks the response.status and returns the result of Promise.resolve() or Promise.reject(), which return a resolved or rejected Promise. This is the first method called in our fetch() chain, if it resolves, we then call our parseJson() method which again returns a Promise from the response.json() call. After this we have an object of the parsed JSON. If the parsing fails the Promise is rejected and the catch statement executes.

Request Object

You can create a new request object with the Request constructor function, which is also part of the proposed standard. A Request instance represents the request piece of a fetch call. By passing fetch a Request you can make advanced and customized requests:

var req = new Request('https://api.acme.com/users.json', {
    method: 'post',
    mode: 'cors',
    redirect: 'follow'
    headers: {
      "Content-type": "application/x-www-form-urlencoded; charset=UTF-8"
    },
    body: 'foo=bar&lorem=ipsum'
  });
// Use request as first parameter to fetch method
fetch(req)
    .then(function() { /* handle response */ });

The first argument is the request URL, and the second argument is an option object that configures the request. Once created, you pass the created object to the fetch method instead of using a string specifying the URL. You can specify following option in the second argument.

  • method - GET, POST, PUT, DELETE, HEAD
  • url - URL of the request
  • headers - associated Headers object
  • referrer - referrer of the request
  • mode - cors, no-cors, same-origin
  • credentials - should cookies go with the request? omit, same-origin
  • redirect - follow, error, manual
  • integrity - subresource integrity value
  • cache - cache mode (default, reload, no-cache)

Response Object

The Response instance will be passed in fetch’s then callback. You can inspect following fields in the response object.

  • type - basic, cors
  • url
  • useFinalURL - Boolean for if url is the final URL
  • status - status code (ex: 200, 404, etc.)
  • ok - Boolean for successful response (status in the range 200-299)
  • statusText - status code (ex: OK)
  • headers - Headers object associated with the response.
// The fetch's `then` gets a Response instance back
fetch('https://api.acme.com/users.json')
    .then(function(responseObj) {
        console.log('status: ', responseObj.status);
    });

Browsers Support

Fetch is supported in Chrome 42+, Opera 29+, and Firefox 39+. Microsoft marked this feature as under consideration. Ironically, XMLHttpRequest gets a replacement just as Internet Explorer finally implemented progress events for the response.

Conclusions

The fetch API is an easier way to make web requests and handle responses than using an XMLHttpRequest. Until it is supported broadly, you’ll have to use a window.fetch JavaScript polyfill.