A. M. Douglas

Why the new fetch API is Broken

There is probably no DOM API more loathed than the XMLHttpRequest, but is the hatred really justified? It probably was when web browsers did not implement it consistently, but these days you can pretty much get by with the same code in every browser. A GET request might look something like this:

var xhr = new XMLHttpRequest();

xhr.open( 'GET', url );

xhr.onreadystatechange = function(){
  if ( this.readyState === 4 && this.status >= 200 && this.status < 300 )
    // do stuff;

xhr.onabort = xhr.onerror = function(){
  console.info( 'There was an error with the request' );


That will more or less work anywhere. And it's not particularly horrific to look at. It's certainly not pretty, but it used to be much worse in the ActiveX days.

Enter fetch

Now there's a new kid in town—the fetch API—and, looking at that spec, web developers around the world should be rejoicing. Finally, something that looks like jQuery but in vanilla JavaScript, thank goodness!

But we're not rejoicing. At least, I'm not, and I've noted a number of others not particularly happy with the new kid. Despite many articles hailing it as the future, and others waving XHR goodbye The first and foremost complaint is the fact that it is Promise-based, and thus does not presently offer any means of aborting a request. This issue is plain to see, and will probably be fixed (one day).

I take issue with something just as fundamental: it leads to ugly code and offers less power than XHR2.

The case against fetch

Let's look at that XHR code again. If I wanted to use that snippet to get another page of my website and load it into the current document, replacing the old page and effectively loading the new page without a full browser refresh, I could obviously parse the response using the HTML parser built into the vanilla DOM, but this wouldn't be necessary thanks to the XHR2 spec, which includes the ability to set a response type. Thus, in order to get a document which can be querySelector'd and so on, all we have to add is this:

xhr.responseType = 'document';

Very, very simple.

Fast-forward to the fetch API. The same code looks like this:

fetch( url, {
  method: 'GET',
  headers: { 'Content-Type' : 'text/html' },
  mode: 'same-origin'
}).then( function( response ){

  if ( response.ok){
    var contentType = response.headers.get( 'Content-Type' );

    if( contentType && contentType.indexOf( 'text/html' ) !== -1 ){
      return response.text().then( function( resptxt ){

        var parser = new DOMParser(),
            doc = parser.parseFromString( resptxt, 'text/html' );

        // do something with the new document

      }).catch( function(){
        console.info( 'Error: No HTML received' )
}).catch( function( response ){
  console.info( 'Error: ' + response.message )

Your eyes do not deceive you: the fetch API actually leads to more verbose code. Here I have to manually set a content type checker, resolve the text from the response Promise and parse the text with the DOM API! It's absurd!

Why fetch then?

Ultimately, this is because the fetch API, despite having poor browser support, is actually more low-level than XHR. XHR is to be defined in fetch in the end, and such things as xhr.responseType = 'document' will probably be defined in much the same way as I have outlined.

This does of course invite the question of when to actually use fetch. The use cases become clear when you consider streaming (fetch responses are Stream objects, and are thus asynchronous by nature) and Service Workers.

Like many low-level APIs, you should stick to using XHR2 unless there's something you specifically need from the fetch API like streams.

Labels: ,


Post a Comment