blog

Courtesy of publicdomainpictures.net

Go Fetch! (JavaScript Fetch API)

by

Long ago, we briefly brushed upon the topic of what has made jQuery such a valuable part of the web developer’s toolset for such a long time – namely, a cleaner interface for interacting with the DOM, and the $.ajax abstraction over XMLHttpRequest.

These days, I would go a step farther and discuss how it has positively influenced browser APIs. jQuery offered a way to find elements using their css selectors, and this eventually gave us document.querySelector and document.querySelectorAll. More recently, browser developers have taken another page from jQuery’s playbook and introduced a new, Promise-based API for making asynchronous requests, and so much more – Fetch.

Why go Fetch? Let’s take a look.

A Comparison

First, let’s take a look at what a simple request using three different APIs might look like – first, our old friend XMLHttpRequest, then jQuery’s ajax, and then Fetch.

XMLHttpRequest
var xhr = new XMLHttpRequest();
xhr.addEventListener('load', function(){
    var json = this.response || JSON.parse(this.responseText);
    console.log(json);
});
xhr.addEventListener('error', function(err){
    console.log("Oh dear, something went wrong: ", err);
});
xhr.responseType = 'json';
xhr.open('get', '//jsonplaceholder.typicode.com/users', true);
xhr.send();

Not terrible, of course, but a little more verbose than we might like, and we don’t get the nice, logical progression of this-then-that in our code, since we have to assign our load and error event handlers before we make the send call.

jQuery
$.ajax({
    type:'GET',
    url:'//jsonplaceholder.typicode.com/users',
    dataType:'json'
}).done(function(data){
    console.log(data);
}).fail(function(jqXHR){
    console.log("Oh dear, something went wrong: ", jqXHR.status, jqXHR.responseText);
});

Ah, much nicer – we can specify all the parameters to the ajax call within a single settings object, and with Deferred support baked in since v1.4, we can dangle a done block after our call, keeping the logic more compact and readable.

Fetch
fetch('//jsonplaceholder.typicode.com/users', {
    method:'GET'
}).then((response) => {
    return response.json();
}).then((json) => {
    console.log(json);
}).catch((err) => {
    console.log("Oh dear, something went wrong: ", err);
});;

You can see the similarities to the $.ajax example, but the fetch API manages to be both a tad more compact – in part thanks to Promises and Arrow Functions – and more powerful in a variety of ways.

For instance, let’s talk a little about the Response readers, and the Response and Request objects.

Response Readers

You’ll notice in the example above that our initial then block returns a call to .json() on the response. What’s happening here is that the response is presented to us as a stream. We could read this stream manually if we wanted to, via response.body.getReader(), but for most purposes, we can rely on one of the built in readers: text, json, formData, blob and arrayBuffer. These readers drain the stream and return the value decoded as one might expect in the form of the specified reader – i.e. using the blob reader on the stream will return its value decoded as a blob object.

This is neat, because it offers us the opportunity to consider how we want to decode any given return – no hacks on top of responseText here. One gotcha to be mindful of, though, is that the readers do ‘drain the stream’, as I noted above, so if you were to return the response to a future then block, and attempt to read again, that read would fail. We’ll talk more about this point next time.

Response & Request

Another nicety of the Fetch api is that we finally gain access to the Response and Request primitives that underlie these requests, allowing us to form our own Response and Request objects. This isn’t likely to come up very often (especially outside of the context of a web worker), but we’ll also be taking a look at how we can use the Response object to produce our own streamable response from cached values next time.

Gotchas

One of the differences between XMLHttpRequest (and thus $.ajax), and fetch is that network returns that indicate failure, like a 400 or 500 response, won’t be considered errors by your fetch promise – if you want them to be errors that are
handled in the catch block, you’ll want to check for them and throw them yourself:

fetch('//jsonplaceholder.typicode.com/users', {
    method:'GET',
    credentials:'omit',
    headers:{
        'Content-Type': 'application/json'
    },
    mode:'cors',
    cache:'no-cache',
    referrer:'no-referrer'
}).then((response) => {
    let status = response.status;
    if (status < 200 || status > 299) throw new Error(status);
}).catch((error) => {
    // Catch our network error here
});

You’ll notice I also included a more comprehensive settings object this time. For the details on all the possible settings, I suggest checking out the links in the further reading section, below. For now, the one we really need to talk about is the credentials property.

By default, XMLHttpRequest will send credentials to requests on the same-origin. Not so for fetch – ‘omit’ is the default value for credentials, so if you need to send cookies up to your server to authenticate a request, for instance, you’ll need to be sure to specify credentials:'include' instead.

Finally, at the moment there’s no way to cancel a fetch request (this is in the works, and will probably coincide with being able to cancel a Promise), there’s no progress events as there are in the XHR2 spec (although you can build your own, should you be so inclined), and there’s no option for synchronous requests (which is good, and sync requests are deprecated and going to disappear in XHR at some future point as well).

These potential gotchas are the usual cost of being on the sharp edge of emerging tech, and many will likely be smoothed over by browser developers or libraries that wrap the fetch functionality.

Next Time

Speaking of libraries that wrap the Fetch functionality, as I’ve been hinting, next time we’ll take a deeper look into actually using the Fetch API, and present and dissect a library that will allow us to cache responses we’ve retrieved with fetch for bandwidth savings and offline usage. Stay tuned.

Further Reading

Image courtesy of commons.wikimedia.com.

+ more

Accurate Timing

Accurate Timing

In many tasks we need to do something at given intervals of time. The most obvious ways may not give you the best results. Time? Meh. The most basic tasks that don't have what you might call CPU-scale time requirements can be handled with the usual language and...

read more