fetch API

By  on  

One of the worst kept secrets about AJAX on the web is that the underlying API for it, XMLHttpRequest, wasn't really made for what we've been using it for.  We've done well to create elegant APIs around XHR but we know we can do better.  Our effort to do better is the fetch API.  Let's have a basic look at the new window.fetch method, available now in Firefox and Chrome Canary.

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) {}
  }
}

// Open, send.
request.open('GET', 'https://davidwalsh.name/ajax-endpoint', true);
request.send(null);

Of course our JavaScript frameworks make XHR more pleasant to work with, but what you see above is a simple example of the XHR mess.

Basic fetch Usage

A fetch function is now provided in the global window scope, with the first argument being the URL:

// url (required), options (optional)
fetch('https://davidwalsh.name/some/url', {
	method: 'get'
}).then(function(response) {
	
}).catch(function(err) {
	// Error :(
});

Much like the updated Battery API, the fetch API uses JavaScript Promises to handle results/callbacks:

// Simple response handling
fetch('https://davidwalsh.name/some/url').then(function(response) {
	
}).catch(function(err) {
	// Error :(
});

// Chaining for more "advanced" handling
fetch('https://davidwalsh.name/some/url').then(function(response) {
	return //...
}).then(function(returnedValue) {
	// ...
}).catch(function(err) {
	// Error :(
});

If you aren't used to then yet, get used to it -- it will soon be everywhere.

Request Headers

The ability to set request headers is important in request flexibility. You can work with request headers by executing new Headers():

// Create an empty Headers instance
var headers = new Headers();

// Add a few headers
headers.append('Content-Type', 'text/plain');
headers.append('X-My-Custom-Header', 'CustomValue');

// Check, get, and set header values
headers.has('Content-Type'); // true
headers.get('Content-Type'); // "text/plain"
headers.set('Content-Type', 'application/json');

// Delete a header
headers.delete('X-My-Custom-Header');

// Add initial values
var headers = new Headers({
	'Content-Type': 'text/plain',
	'X-My-Custom-Header': 'CustomValue'
});

You can use the append, has, get, set, and delete methods to modify request headers. To use request headers, create a Request instance :

var request = new Request('https://davidwalsh.name/some-url', {
	headers: new Headers({
		'Content-Type': 'text/plain'
	})
});

fetch(request).then(function() { /* handle response */ });

Let's have a look at what Response and Request do!

Request

A Request instance represents the request piece of a fetch call. By passing fetch a Request you can make advanced and customized requests:

  • 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)

A sample Request usage may look like:

var request = new Request('https://davidwalsh.name/users.json', {
	method: 'POST', 
	mode: 'cors', 
	redirect: 'follow',
	headers: new Headers({
		'Content-Type': 'text/plain'
	})
});

// Now use it!
fetch(request).then(function() { /* handle response */ });

Only the first parameter, the URL, is required. Each property becomes read only once the Request instance has been created. Also important to note that Request has a clone method which is important when using fetch within the Service Worker API -- a Request is a stream and thus must be cloned when passing to another fetch call.

The fetch signature, however, acts like Request so you could also do:

fetch('https://davidwalsh.name/users.json', {
	method: 'POST', 
	mode: 'cors', 
	redirect: 'follow',
	headers: new Headers({
		'Content-Type': 'text/plain'
	})
}).then(function() { /* handle response */ });

You'll likely only use Request instances within Service Workers since the Request and fetch signatures can be the same. ServiceWorker post coming soon!

Response

The fetch's then method is provided a Response instance but you can also manually create Response objects yourself -- another situation you may encounter when using service workers. With a Response you can configure:

  • 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.
// Create your own response for service worker testing
// new Response(BODY, OPTIONS)
var response = new Response('.....', {
	ok: false,
	status: 404,
	url: '/'
});

// The fetch's `then` gets a Response instance back
fetch('https://davidwalsh.name/')
	.then(function(responseObj) {
		console.log('status: ', responseObj.status);
	});

The Response also provides the following methods:

  • clone() - Creates a clone of a Response object.
  • error() - Returns a new Response object associated with a network error.
  • redirect() - Creates a new response with a different URL.
  • arrayBuffer() - Returns a promise that resolves with an ArrayBuffer.
  • blob() - Returns a promise that resolves with a Blob.
  • formData() - Returns a promise that resolves with a FormData object.
  • json() - Returns a promise that resolves with a JSON object.
  • text() - Returns a promise that resolves with a USVString (text).

Handling JSON

Let's say you make a request for JSON -- the resulting callback data has a json method for converting the raw data to a JavaScript object:

fetch('https://davidwalsh.name/demo/arsenal.json').then(function(response) { 
	// Convert to JSON
	return response.json();
}).then(function(j) {
	// Yay, `j` is a JavaScript object
	console.log(j); 
});

Of course that's a simple JSON.parse(jsonString), but the json method is a handy shortcut.

Handling Basic Text/HTML Responses

JSON isn't always the desired request response format so here's how you can work with an HTML or text response:

fetch('/next/page')
  .then(function(response) {
    return response.text();
  }).then(function(text) { 
  	// <!DOCTYPE ....
  	console.log(text); 
  });

You can get the response text via chaining the Promise's then method along with the text() method.

Handling Blob Responses

If you want to load an image via fetch, for example, that will be a bit different:

fetch('https://davidwalsh.name/flowers.jpg')
	.then(function(response) {
	  return response.blob();
	})
	.then(function(imageBlob) {
	  document.querySelector('img').src = URL.createObjectURL(imageBlob);
	});

The blob() method of the Body mixin takes a Response stream and reads it to completion.

Posting Form Data

Another common use case for AJAX is sending form data -- here's how you would use fetch to post form data:

fetch('https://davidwalsh.name/submit', {
	method: 'post',
	body: new FormData(document.getElementById('comment-form'))
});

And if you want to POST JSON to the server:

fetch('https://davidwalsh.name/submit-json', {
	method: 'post',
	body: JSON.stringify({
		email: document.getElementById('email').value
		answer: document.getElementById('answer').value
	})
});

Very easy, very eye-pleasing as well!

Unwritten Story

While fetch is a nicer API to use, the API current doesn't allow for canceling a request, which makes it a non-starter for many developers.

The new fetch API seems much saner and simpler to use than XHR.  After all, it was created so that we could do AJAX the right way; fetch has the advantage of hindsight.  I can't wait until fetch is more broadly supported!

This is meant to be an introduction to fetch.  For a more in depth look, please visit Introduction to Fetch.  And if you're looking for a polyfill, check out GitHub's implementation.

Raygun Pulse

Recent Features

  • By
    Create Namespaced Classes with MooTools

    MooTools has always gotten a bit of grief for not inherently using and standardizing namespaced-based JavaScript classes like the Dojo Toolkit does.  Many developers create their classes as globals which is generally frowned up.  I mostly disagree with that stance, but each to their own.  In any event...

  • By
    Send Text Messages with PHP

    Kids these days, I tell ya.  All they care about is the technology.  The video games.  The bottled water.  Oh, and the texting, always the texting.  Back in my day, all we had was...OK, I had all of these things too.  But I still don't get...

Incredible Demos

  • By
    Create a Dynamic Flickr Image Search with the Dojo Toolkit

    The Dojo Toolkit is a treasure chest of great JavaScript classes.  You can find basic JavaScript functionality classes for AJAX, node manipulation, animations, and the like within Dojo.  You can find elegant, functional UI widgets like DropDown Menus, tabbed interfaces, and form element replacements within...

  • By
    Redacted Font

    Back when I created client websites, one of the many things that frustrated me was the initial design handoff.  It would always go like this: Work hard to incorporate client's ideas, dream up awesome design. Create said design, using Lorem Ipsum text Send initial design concept to the client...

Discussion

  1. window.fetch went live in Chrome with yesterday’s Chrome Stable release.

  2. MaxArt

    One of the features that are lost with fetch is monitoring the progression of the upload/download. And I don’t quite know if it can be restored nicely with a promise-based interface.

  3. I can’t wait until Spartan 2.0? becomes the minimum browser we need to support! (Until then window.fetch simply isn’t an option without coding a fallback for IE support) http://caniuse.com/#search=fetch That all said, I seriously can’t wait – it will be awesome to be able to use it.

    • Tony

      Agreed, can’t wait to start using it. Don’t mind writing a fallback if need be, this is forward progress =>

    • All IE versions and *sadly* Safari don’t support fetch API yet. The syntax and how it works are very interesting. Can’t wait to use it in my projects.

  4. Dave

    David, I read your posts and I get confused feelings. I’m excited using the code in the examples but feel like a bad developer for not knowing this was available to me already. Then I find out it’s actually not available to me. Might be nice to play with but not for use in production and it would be nice if it was noted that this exciting new API is new and not available in most browsers. I’m still not even using native promises because of legacy browsers. jQuery has let me get use to coding using promises, and when it’s available natively in all the browsers I need to support, I’ll move native.

    Just try and make it clear that use of the example code may not be available in all browsers just yet. Maybe you could add tags for browsers that support whatever API or other features you are discussing.

    • Please re-read the last sentence of the intro.

  5. deDogs

    What is different between using fetch and jQuery.ajax? Do we really needing something else on the global scope, which adds additional project weight. Some occasions fetch may not be needed in a project.

    • fetch is native and jQuery.ajax is a library method.

      If you’re looking to remove something from the global scope, jQuery is probably the one to remove. :)

    • haha I agree. Although I hate jquery for stealing code to claim but hey whatever!. You just re-share and respost other peoples work David. Lets not get big headed we all do it. jQuery just simply removed you from the pic and in return they saved users time. I enjoy coding my won faster more direct to the punch functions and handles my self. I am only complaining because you are a cocky prick. You’re welcome!

  6. Hey David, great writeup, thanks a lot!
    I’m a bit confused about the JSON part though:
    You say

  7. Hey David, great writeup, thanks a lot!
    I’m a bit confused about the JSON part though:
    You say

    return response.json()

    is like calling

    JSON.stringify()

    on the response body, but the next

    then

    takes a js object as a parameter — so isn’t it more like

    JSON.parse(responseBody)

    then?

  8. I don’t understand why you are omitting that progress notification as well as an equivalent of abort are missing.

    I also don’t understand why you wen’t full polyfill down to IE 5 for XHR, underlying how mess is that, and didn’t show how worst would be the full polyfill down to IE 5 for fetch, including the Promises polyfill.

    It looks like the community is putting effort to emphasize so much how bad is XHR …. this is the part I like the least about the entire fetch story.

    • I added a block about the inability to cancel! :)

    • Not sure if you heard about the SAM pattern and the corresponding SAFE middleware, it supports cancellation (as in ignore the response) in a generic and very simple way (what I call “action hang back”).

  9. and could you also drop that polyfill which is completely unfair or out of context? Do you really have to show that? Because it’s not clear what’s the point.

    Show 6 LOC to get fetch cross platform down to old IE and in few lines of code, including standard Promise polyfill, or maybe simply show the difference between APIs.

    XHR is just fine, where does Fetch API shine? Why should we consider it? Which browser is compatible? How big is the polyfill?

    The argument XHR is ugly, with all the problems we have on the Web, feels so childish.

    Apologies for the comment, I just don’t understand this “mafia” style war against XHR.

    “There’s a new API, it does things in a Promise friendly way, here how to use it => ” would be that kind of post I’d expect from your blog.

    Not another “XHR is ugly, long glory to the beautiful Fetch API”, I am sure you know what I mean.

    Regards

    • I just re-read my post, thinking by your comments that I may have said something too harsh, but I’m not seeing it…

      “One of the worst kept secrets about AJAX on the web is that the underlying API for it, XMLHttpRequest, wasn’t really made for what we’ve been using it for.” – I feel that’s true.

      “We’ve done well to create elegant APIs around XHR but we know we can do better. Our effort to do better is the fetch API.”” – I feel that’s also true.

      Nowhere did I use the word “ugly”. I did use “mess”, but getting XHR is a bit of a mess, no? Three different checks?

      I’m appreciate of XHR and how we’ve used it, but I’m excited about a new API which was developed with XHR history in mind.

    • you don’t have 3 different checks … nobody does. Same you don’t have different checks to grab fetch, because it’s not cross platform. XHR is cross platform, and if you want to support IE < 9 you need only 2 extra checks. What do you need to do in order to have fetch cross platform? just 2 checks and few lines to have it? Or you need the entire fetch polyfill plus the entire Promises polyfill? What is the point of showing a polyfill for a well supported API, which is XHR, when the new one is not even supported at all? This is the part I don't understand, you are saying: here, few lines of code to have a cross browser API that works down to IE5 … compare against one that does not even exists, how better is the new one?

      I don't know … because the new one does not show how many lines of code you need to have it, while XHR can be shimmed in just those few lines.

      So, compare XHR, its ease everyone knows, its cross browser compatibility, against something that will never work down to IE < 9 and every other Mobile Web browser … how pointless is this comparison? Do you really not get it? You compared a library to polyfll an API agtainst an API that will need more libraries in order to be supported, than just few lines.

      I don't how else I can explain this, sorry.

    • I understand your viewpoint now. I’ll think on it, re-evaluate what you’ve written, and update my post as I see fit. Thanks!

    • or to make it a TL;DR … if just getting XHR is considered a mess for few lines of code, show how mess would be to have fetch API compatible with the same amount of browsers in just few lines of code. Here the catch: you cannot. If you need only 3 checks to polyfill XHR, you need 10X the amount of code and libraries to have Fetch API equivalently compatible and supported cross platform and cross browsers. Which part I am not explaining properly here?

    • Andrea: I understand what you’re saying. Chill.

    • One particular thing in which I totally agree with Andrea here is that XMLHttpRequest example is completely unfair as ActiveX polyfill is not needed for IE>6 (and modern projects usually have even higher bar, at least IE8+). This part should be updated.

    • “when the new one is not even supported at all?” – this is not true. Already using in FF and Chrome without polyfills.

  10. Florent

    Maybe worth to note that with async/await in ES7 (or with task.js with ES6), it will be funnier:

    (async function() {
      let response = await fetch('http://davidwalsh.name/demo/arsenal.json');
      let json = await response.json();
      console.log(json);
    })();
    

    Or with task.js:

    spawn(function* () {
      let response = yield fetch('http://davidwalsh.name/demo/arsenal.json');
      let json = yield response.json();
      console.log(json);
    }
    
    • I like the await syntax a lot.

  11. Ambrose

    Stop trying to make fetch happen. It’s not going to happen.

    • Sean

      “Stop trying to make X happen. It’s not going to happen.”
      Yep, let’s just abandon all technological progression because it isn’t going to be immediately used en masse.

    • He’s quoting a funny movie :)

  12. vsync

    What about cross-domain? This is totally ignored in this article and comments, oddly :/

    • It’s just another API for same old good XMLHttpRequest, cross-domain behaves just like always did.

  13. Don’t forget, by default, the fetch() won’t send cookies like good old jQuery did. To change that use {credentials: 'same-origin'}, like this:

     fetch('/amisignedin', {credentials: 'same-origin'})
     .then(....)
  14. Mike Holloway

    This is one article I keep coming back to because I find it informative and clear. Thanks dude.

  15. fetch() really seems to be the way to go these days, thanks for the write up.

  16. Miguel

    Hi!

    Every place i see samples of fetch, i see two calls to “then” chained, as you did in here

    fetch('http://davidwalsh.name/demo/arsenal.json').then(function(response) { 
    	// Convert to JSON
    	return response.json();
    }).then(function(j) {
    	// Yay, j is a JavaScript object
    	console.log(j); 
    });
    

    Is it needed? is it just a good practice? I mean, could you just have the following?

    fetch('http://davidwalsh.name/demo/arsenal.json').then(function(response) { 
    	// Convert to JSON
    	let j = response.json();
            return j.aProperty || whateverWith(j);
    });
    

    Thank you :-)

    • response.json() returns a promise (representing the process to convert the string to JSON). That’s why you need the second then()!

    • It’s not too clear in this article, but at the time the first promise resolves, we do NOT YET have the response body (the part that will be parsed to JSON) so you can think of .json() part as something like .setParserToUseJSON() and then returns another promise when the body has been received and parsed.

      I didn’t understand the fetch API well until I finally understood promises. A then handler that returns another promise is special and really unlocks the power of promises, but it’s not easy for a newcomer to understand by reading example code!

      @DavidWalsh: I think it might be confusing to readers to say “the resulting callback data has a json method […] Of course that’s a simple JSON.parse(jsonString), but the json method is a handy shortcut.”

      It’s not really a shortcut for JSON.parse alone, but does some magic with response buffering and parsing and eventually *resolves* to a result similar to that of calling JSON.parse!

      Might need some more explaining because I remember when I first came to fetch thinking “This is complicated by all this Promise stuff and double then.. why don’t we just have a simple node-style callback?” But now I understand how this abstracts the underlying process into a nice Promise API.

    • Sangeet Agarwal

      wrote a pen to help understand what might be happening behind the scenes, thought I should share it, I think this should approximate what mitht be happening …
      See the Pen fetch Api pseudo code by Sangeet (@Sangeet) on CodePen.”>http://codepen.io/Sangeet/pen/BzJOwj/” rel=”nofollow”>fetch Api pseudo code by Sangeet (@Sangeet) on CodePen.

      fetch Api pseudo code by Sangeet (@Sangeet) on CodePen." data-user="Sangeet">
  17. Hey David! Just a heads up: looks like text starting with “Response” heading is incorrectly wrapped inside h2 up to the next heading.

    P.S. Thanks for awesome reading!

  18. “One of the worst kept secrets about AJAX on the web is that the underlying API for it, XMLHttpRequest, wasn’t really made for what we’ve been using it for.”

    I feel like this is so true for _everything_ on the web :)

    Great article, David!

  19. Dusty

    I would like to access the certificate on the https server I connect to, to verify its fingerprint.

    Is it possible to id using fetch? If not, how could I do it?

  20. Johan

    When I run the example code from your post

    var request = new Request('/some-url', {
    	headers: new Headers({
    		'Content-Type': 'text/plain'
    	})
    });
    
    fetch(request).then(function() { /* handle response */ });

    in Firefox (version 47), it sends a request to https://davidwalsh.name/[object%20Object]. The Content-Type is nowhere to be seen, either. (And it should have been Accept, by the way.)

Wrap your code in <pre class="{language}"></pre> tags, link to a GitHub gist, JSFiddle fiddle, or CodePen pen to embed!