Cancelling Async Work with AbortController

A
Author
22 Apr, 2026 1 min read 680 views

Users navigate away, type a new search or close a dialog while a request is still running. AbortController lets you cancel that work instead of letting it finish uselessly or, worse, overwrite newer results.

Cancel a fetch

const controller = new AbortController();

fetch('/api/report', { signal: controller.signal })
  .then((r) => r.json())
  .then(render)
  .catch((err) => {
    if (err.name === 'AbortError') return; // cancelled on purpose
    showError(err);
  });

cancelButton.onclick = () => controller.abort();

Only the latest search wins

let current;

async function search(query) {
  current?.abort();                    // cancel the previous request
  current = new AbortController();
  try {
    const res = await fetch(`/api/search?q=${encodeURIComponent(query)}`, { signal: current.signal });
    renderResults(await res.json());
  } catch (err) {
    if (err.name !== 'AbortError') throw err;
  }
}

Timeouts in one line

const res = await fetch(url, { signal: AbortSignal.timeout(5000) }); // TimeoutError after 5s

Combine signals

const signal = AbortSignal.any([userController.signal, AbortSignal.timeout(10_000)]);
await fetch(url, { signal }); // cancelled by the user OR after 10 seconds

Make your own functions cancellable

async function processAll(items, { signal } = {}) {
  for (const item of items) {
    signal?.throwIfAborted();          // stop between steps
    await processItem(item);
  }
}

function sleep(ms, { signal } = {}) {
  return new Promise((resolve, reject) => {
    const id = setTimeout(resolve, ms);
    signal?.addEventListener('abort', () => {
      clearTimeout(id);
      reject(signal.reason);
    }, { once: true });
  });
}

In React effects

useEffect(() => {
  const controller = new AbortController();
  fetch(`/api/users/${id}`, { signal: controller.signal }).then(...);
  return () => controller.abort(); // runs when id changes or the component unmounts
}, [id]);

Event listeners accept a signal too: el.addEventListener('click', fn, { signal }) removes the listener when the signal aborts.

Was this article helpful?

Comments

No comments yet. Have a question or a tip? Be the first to share it.

Leave a comment

Comments are reviewed before they appear.