4 common mistakes front-end developers make when using fetch

Fetch looks like $.ajax but it isn't

· 3 min read
Gretchen, from the movie Mean Girls, next to the text "So fetch!"
I felt obligated to make the Mean Girls reference

fetch is the hot new way to make HTTP requests in the browser. More than just being a better, more ergonomic API than XMLHttpRequest, it brings a lot of exciting new capabilities like response streaming, more control over credentials and CORS requests, and integration with ServiceWorkers and the Cache API.

As I’ve researched fetch, used it, and seen it used in the wild, I’ve found that there’s a lot of common errors that even really experienced developers make. I think much of this has to do with the fact that fetch’s API, on the surface, looks and behaves much like jQuery’s $.ajax, angularJS’s $http, axios, and others. There’s some important differences and most of those differences stem from fetch’s design as a low-level network request primitive.

1. Assuming that the promise rejects on HTTP error statuses

Fetch is promise based, so it’s easy to assume that it will reject it’s promise if it receives an HTTP error status, like 404 or 500. Just like raw XMLHttpRequest, that’s not the case with fetch — it only rejects it’s promise on a “network error” (sort of a confusing term, but that’s what the spec says). If you get an http error from a server, the server is working and is actively processing requests, so a network error represents when the server can’t be reached at all (e.g connection refused or name not resolved) or there’s an error with the request configuration, for example.

For example, the ftp protocol is not supported by fetch so this will return a rejected promise and a network request will never be made:

fetch('ftp://example.com')
  .catch(err => console.error('Caught error: ', err))

But requesting a URL from a server that will return a 404 will not fail.

fetch('https://jsonplaceholder.typicode.com/404')
  .then(res => console.log('response: ', res))
  .catch(console.error)

I think most developers find it useful in web applications to reject a promise if an HTTP error status is returned. To do that, we just need to check the ok property and reject if it’s false.

fetch('https://jsonplaceholder.typicode.com/404')
  .then(res => {
    if(res.ok) {
      return res;
    } else {
      throw Error(`Request rejected with status ${res.status}`);
    }
  })
  .catch(console.error)

2. Forgetting to include credentials

(Note: this is outdated now, fetch on all browsers sets credentials to same-origin automatically.)

Unlike XHR, fetch does not include any cookies in the request by default. So if requests against your API require cookie based authorization (most web apps do) this has to be there or your call will likely return 401 Unauthorized.

This is easy:

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

Note that if you’re using the fetch polyfill, you can (unfortunately) accidentally forget this and everything will still work like you’re passing credentials: 'include'. This is because it’s just using XHR under the hood, which has this behavior automatically.

3. Forgetting to set the Content-Type to application/json when POSTing JSON

$http, axios, and others tend to default to setting this header for you, so it’s easy to forget.

fetch('https://cameronnokes.com/slack-ron-swanson-quote-bot/ron', {
  method: 'POST',
  body: JSON.stringify({text: 'bacon'}),
  headers: {'Content-Type': 'application/json'}
})
.then(res => res.json())
.then(console.log)

(This API is a part of my Ron Swanson quote slack bot and does actually work)

If you don’t include that header, your server might return a 400 Bad Request because the endpoint doesn’t support a plain/text content type or it just won’t be able to process the body. It depends on your API’s implementation. Regardless, your API will probably return an HTTP error status.

4. Manually writing out complicated query params in a string

I’ve seen code like this:

fetch('/endpoint?foo=${n > 1 ? 123 : 456}&bar=abc&query=${encodeURIComponent(someString || '')}

That’s not completely terrible or anything, but I think it could be easier to read and less error prone if we let the new URLSearchParams class and a dash of lodash do a bit more work for us.

The URLSearchParams class is great and handles encoding everything properly, joining an object into a string, etc. I think that’s easier to read and easier to maintain.

Conclusion

Fetch is awesome but enjoy responsibly 😀