Skip to content

The Fetch API

Learn all about the Fetch API, the modern approach to asynchronous network requests which uses Promises as a building block

Introduction to the Fetch API

Since IE5 was released in 1998, we’ve had the option to make asynchronous network calls in the browser using XMLHttpRequest (XHR).

Quite a few years after this, GMail and other rich apps made heavy use of it, and made the approach so popular that it had to have a name: AJAX.

Working directly with the XMLHttpRequest has always been a pain and it was almost always abstracted by some library, in particular jQuery has its own helper functions built around it:

and so on.

They had a huge impact on making this more accessible in particular with regards to making sure all worked on older browsers as well.

The Fetch API, has been standardized as a modern approach to asynchronous network requests, and uses Promises as a building block.

Fetch has a good support across the major browsers, except IE.

The browser support for the Fetch API

The polyfill https://github.com/github/fetch released by GitHub allows us to use fetch on any browser.

Using Fetch

Starting to use Fetch for GET requests is very simple:

fetch('/file.json')

and you’re already using it: fetch is going to make an HTTP request to get the file.json resource on the same domain.

As you can see, the fetch function is available in the global window scope.

Now let’s make this a bit more useful, let’s actually see what the content of the file is:

fetch('./file.json')
  .then((response) => response.json())
  .then((data) => console.log(data))

Calling fetch() returns a promise. We can then wait for the promise to resolve by passing a handler with the then() method of the promise.

That handler receives the return value of the fetch promise, a Response object.

We’ll see this object in detail in the next section.

Catching errors

Since fetch() returns a promise, we can use the catch method of the promise to intercept any error occurring during the execution of the request, and the processing done in the then callbacks:

fetch('./file.json')
  .then((response) => {
    //...
  })
  .catch((err) => console.error(err))

Another way of catching errors is to manage them in the first then:

fetch('./file.json')
  .then((response) => {
    if (!response.ok) {
      throw Error(response.statusText)
    }
    return response
  })
  .then((response) => {
    //...
  })

Response Object

The Response Object returned by a fetch() call contains all the information about the request and the response of the network request.

Metadata

headers

Accessing the headers property on the response object gives you the ability to look into the HTTP headers returned by the request:

fetch('./file.json').then((response) => {
  console.log(response.headers.get('Content-Type'))
  console.log(response.headers.get('Date'))
})

Request headers for fetch

status

This property is an integer number representing the HTTP response status.

fetch('./file.json').then((response) => console.log(response.status))

statusText

statusText is a property representing the status message of the response. If the request is successful, the status is OK.

fetch('./file.json').then((response) => console.log(response.statusText))

url

url represents the full URL of the property that we fetched.

fetch('./file.json').then((response) => console.log(response.url))

Body content

A response has a body, accessible using several methods:

All those methods return a promise. Examples:

fetch('./file.json')
  .then((response) => response.text())
  .then((body) => console.log(body))
fetch('./file.json')
  .then((response) => response.json())
  .then((body) => console.log(body))

Get JSON

The same can be written using the ES2017 async functions:

;(async () => {
  const response = await fetch('./file.json')
  const data = await response.json()
  console.log(data)
})()

Request Object

The Request object represents a resource request, and it’s usually created using the new Request() API.

Example:

const req = new Request('/api/todos')

The Request object offers several read-only properties to inspect the resource request details, including

And exposes several methods including json(), text() and formData() to process the body of the request.

The full API can be found at https://developer.mozilla.org/docs/Web/API/Request

Request headers

Being able to set the HTTP request header is essential, and fetch gives us the ability to do this using the Headers object:

const headers = new Headers()
headers.append('Content-Type', 'application/json')

or:

const headers = new Headers({
  'Content-Type': 'application/json',
})

To attach the headers to the request, we use the Request object, and pass it to fetch() instead of passing the URL.

Instead of:

fetch('./file.json')

we do

const request = new Request('./file.json', {
  headers: new Headers({
    'Content-Type': 'application/json',
  }),
})
fetch(request)

The Headers object is not limited to setting value, but we can also query it:

headers.has('Content-Type')
headers.get('Content-Type')

and we can delete a header that was previously set:

headers.delete('X-My-Custom-Header')

POST Requests

Fetch also allows to use any other HTTP method in your request: POST, PUT, DELETE or OPTIONS.

Specify the method in the method property of the request, and pass additional parameters in the header and in the request body:

Example of a POST request:

const options = {
  method: 'post',
  headers: {
    'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
  },
  body: 'name=Flavio&test=1',
}

fetch(url, options).catch((err) => {
  console.error('Request failed', err)
})

How to cancel a fetch request

For a few years after fetch was introduced, there was no way to abort a request once opened.

Now we can, thanks to the introduction of AbortController and AbortSignal, a generic API to notify abort events

You integrate this API by passing a signal as a fetch parameter:

const controller = new AbortController()
const signal = controller.signal

fetch('./file.json', { signal })

You can set a timeout that fires an abort event 5 seconds after the fetch request has started, to cancel it:

setTimeout(() => controller.abort(), 5 * 1000)

Conveniently, if the fetch already returned, calling abort() won’t cause any error.

When an abort signal occurs, fetch will reject the promise with a DOMException named AbortError:

fetch('./file.json', { signal })
  .then((response) => response.text())
  .then((text) => console.log(text))
  .catch((err) => {
    if (err.name === 'AbortError') {
      console.error('Fetch aborted')
    } else {
      console.error('Another error', err)
    }
  })

→ Here's my latest YouTube video

→ I wrote 17 books to help you become a better developer, download them all at $0 cost by joining my newsletter

JOIN MY CODING BOOTCAMP, an amazing cohort course that will be a huge step up in your coding career - covering React, Next.js - next edition February 2025

Bootcamp 2025

Join the waiting list