JavaScript AbortController: Cancel Async Requests

The AbortController interface in JavaScript provides a standardized way to cancel asynchronous operations, such as Fetch API requests, streams, or custom asynchronous tasks, before they complete. By pairing an AbortController instance with an AbortSignal, developers can communicate cancellation events across different parts of an application, preventing memory leaks, reducing unnecessary network bandwidth, and avoiding race conditions when handling outdated data.

How AbortController Works

The cancellation mechanism relies on two primary components:

  1. AbortController: The controller object that initiates the abort action via its .abort() method.
  2. AbortSignal: A read-only property (controller.signal) passed to the asynchronous operation, acting as an event listener that notifies the operation when an abort has been requested.

When controller.abort() is invoked, the associated AbortSignal changes its aborted state to true and dispatches an abort event. Any consumer observing that signal immediately terminates its operation.


Canceling a Fetch Request

The most common use case for AbortController is canceling HTTP requests initiated by the Fetch API.

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

// Pass the signal inside the fetch options object
fetch('https://api.example.com/data', { signal })
  .then(response => response.json())
  .then(data => console.log('Data received:', data))
  .catch(error => {
    if (error.name === 'AbortError') {
      console.log('Fetch request was successfully canceled.');
    } else {
      console.error('An unexpected error occurred:', error);
    }
  });

// Cancel the request
controller.abort();

When controller.abort() runs, the fetch() promise rejects with a DOMException named AbortError. Wrapping the request in a standard try...catch or .catch() block allows you to intercept this error and handle the cancellation gracefully without breaking application flow.


Canceling Multiple Requests Simultaneously

A single AbortSignal can be assigned to multiple asynchronous operations. Calling .abort() on the parent controller cancels all linked operations at once:

const controller = new AbortController();
const { signal } = controller;

const fetchUsers = fetch('/api/users', { signal });
const fetchPosts = fetch('/api/posts', { signal });

// Cancels both requests simultaneously
controller.abort();

Canceling Custom Asynchronous Operations

AbortSignal is an EventTarget, meaning it can be used to cancel custom operations like timers or background calculations using the abort event listener:

function delay(ms, signal) {
  return new Promise((resolve, reject) => {
    if (signal?.aborted) {
      return reject(new DOMException('Aborted', 'AbortError'));
    }

    const timer = setTimeout(() => {
      resolve('Operation completed');
    }, ms);

    signal?.addEventListener('abort', () => {
      clearTimeout(timer);
      reject(new DOMException('Aborted', 'AbortError'));
    });
  });
}

const controller = new AbortController();
delay(5000, controller.signal).catch(err => console.log(err.message));

// Cancel the timer after 1 second
setTimeout(() => controller.abort(), 1000);

Automatic Timeouts with AbortSignal.timeout()

Modern JavaScript environments support AbortSignal.timeout(), which automatically creates a signal that triggers an abort after a specified duration:

// Automatically aborts if the request takes longer than 3 seconds
fetch('https://api.example.com/data', { signal: AbortSignal.timeout(3000) })
  .then(response => response.json())
  .catch(error => {
    if (error.name === 'TimeoutError') {
      console.log('Request timed out.');
    }
  });

Key Practical Applications