JavaScriptBrowser APIs

AbortController

Lets you cancel an asynchronous operation, e.g. a fetch() request, and detach many event handlers at once with a single abort() call.

Returns
A new AbortController object with a signal property.
On this page

Example

#
JavaScript
const controller = new AbortController()
const { signal } = controller

signal.addEventListener('abort', () => console.log('Aborted:', signal.reason.name))

const request = fetch('data:application/json,{"courses":12}', { signal })
controller.abort()

try {
  await request
} catch (error) {
  console.log('fetch rejected:', error.name)
}

console.log(signal.aborted)
Console
Aborted: AbortError
fetch rejected: AbortError
true

The abort event fires right away, during controller.abort(). The request’s promise is rejected with an AbortError, caught by the catch block.

Definition and usage

#

AbortController consists of two parts: a controller with an abort() method, and a signal (controller.signal) that you pass to an operation, e.g. fetch(url, { signal }). Calling abort() tells every operation using that signal to stop. An aborted fetch() request rejects its promise with an error named AbortError.

The signal has an aborted property (whether it was aborted), a reason (the reason for aborting: by default the AbortError error, but you can pass your own to abort()), and fires an abort event. The signal.throwIfAborted() method throws the abort reason if the signal is already aborted, which is handy in longer functions. A single controller only works once: an aborted signal stays aborted forever, so create a new controller for the next operation.

You can pass the same signal to addEventListener() too, through the signal option. After abort(), all such handlers get detached, without having to keep track of every function individually. Static methods also produce ready-made signals: AbortSignal.timeout(ms) aborts an operation after a given time (with a TimeoutError), and the newer AbortSignal.any([...]) combines several signals into one.

Syntax

#
Syntax
const controller = new AbortController()
controller.signal
controller.abort()
controller.abort(reason)

signal.aborted
signal.reason
signal.throwIfAborted()
AbortSignal.timeout(ms)

Parameters

#
  • reason

    optional, any value

    The reason for aborting, passed to abort(). It ends up in signal.reason and in the error rejecting the promise. By default it’s a DOMException error named AbortError.

More examples

#
Cancelling the previous search
JavaScript
let controller

async function search(query) {
  controller?.abort()
  controller = new AbortController()

  try {
    const response = await fetch(`data:application/json,{"query":"${query}"}`, {
      signal: controller.signal
    })
    const data = await response.json()
    console.log('Results for:', data.query)
  } catch (error) {
    if (error.name !== 'AbortError') throw error
    console.log('Cancelled:', query)
  }
}

search('ja')
search('jav')
search('java')
Console
Cancelled: ja
Cancelled: jav
Results for: java

Every new search aborts the previous request, so you only get a result for the last piece of text. This is how a search box that searches as you type works.

Detaching several event handlers at once
JavaScript
const button = document.querySelector('#start')
const controller = new AbortController()
const { signal } = controller

button.addEventListener('click', () => console.log('Quiz started'), { signal })
button.addEventListener('click', () => console.log('Timer turned on'), { signal })
document.addEventListener('keydown', () => console.log('Key pressed'), { signal })

button.click()
controller.abort()
button.click()
console.log('Handlers detached:', signal.aborted)
Console
Quiz started
Timer turned on
Handlers detached: true

After controller.abort(), the second click prints nothing. All three handlers were detached with a single call.

A function of your own that can be cancelled
JavaScript
function wait(ms, signal) {
  return new Promise((resolve, reject) => {
    const timer = setTimeout(resolve, ms)
    signal.addEventListener(
      'abort',
      () => {
        clearTimeout(timer)
        reject(signal.reason)
      },
      { once: true }
    )
  })
}

const controller = new AbortController()
setTimeout(() => controller.abort(new Error('Response time limit exceeded')), 200)

try {
  await wait(1000, controller.signal)
  console.log('Response on time')
} catch (error) {
  console.log(error.message)
}
Console
Response time limit exceeded

The wait() function listens for the abort event on the signal and rejects the promise with the reason passed to abort(). You can add cancellation to your own asynchronous operations the same way.

Browser support

#

Widely available · since 2019

Works in all modern browsers, phones included. You can use it without worry.

  • Chrome
  • Edge
  • Firefox
  • Safari

Good practices

#
  • Create a new AbortController for each new operation. An aborted signal stays aborted forever and would immediately abort every following request too.
  • In a catch block, tell an abort apart from a real error with error.name === "AbortError". A cancellation by the user usually doesn’t need an error message.
  • For a simple timeout, fetch(url, { signal: AbortSignal.timeout(5000) }) is enough, with no need to create a controller.

Related entries

#

Spotted a mistake or missing an example? Write to us.