Engrain by ByteRush

JS.11.04 Mid-level· 11 minCore — asked often

Cancellation with AbortController: stopping work nobody needs

Cancel fetch requests and your own async functions with AbortController and AbortSignal. Timeouts with AbortSignal.timeout, combining signals with any, and cleanup in React effects.

Comes up in: JavaScript, Coding, React

Learn#

Rohit’s search box sends a request on every keystroke. Typing “shoes” fires five requests, and they keep downloading after the user has moved on. On a slow 3G connection, the result for “sh” can even arrive last.

The fix is cancellation: telling work that nobody needs its result any more.

An AbortController has one job: controller.abort(). Its signal is an abort signal, which you pass to the work. fetch(url, { signal }) stops the request and rejects with an AbortError when the signal aborts.

function sleep(ms, { signal } = {}) {
  return new Promise((resolve, reject) => {
    signal?.throwIfAborted();
    const id = setTimeout(resolve, ms);
    signal?.addEventListener("abort", () => {
      clearTimeout(id);
      reject(signal.reason);
    }, { once: true });
  });
}
const controller = new AbortController();
setTimeout(() => controller.abort(), 50);
try { await sleep(1000, { signal: controller.signal }); }
catch (e) { console.log(e.name, controller.signal.aborted); }
try { await sleep(1000, { signal: AbortSignal.timeout(50) }); }
catch (e) { console.log(e.name); }

Output (verified)

AbortError true
TimeoutError
Cancelling an old search requestStep 1 of 4

Network

  • GET /search?q=sh …

State

  • signal.aborted = false

The search starts a request and passes controller.signal to it.

Read the steps as text
  1. The search starts a request and passes controller.signal to it.
  2. The user types again. The code calls controller.abort().
  3. The signal fires its abort event. fetch stops the request and rejects with an AbortError.
  4. A new controller is created for the new query. Old results can no longer arrive.

Predict#

Predict first

You call controller.abort() after the fetch promise has already resolved and you have the Response. What happens?

Interview angle#

Say it like this — about 30 seconds

“I create an AbortController and pass its signal to fetch. Calling abort stops the request and fetch rejects with an AbortError, which I ignore in the UI. For timeouts I use AbortSignal.timeout, and AbortSignal.any combines a user signal with a timeout. My own async functions accept a signal, check throwIfAborted, and listen for the abort event to clear timers. In a React effect, the cleanup function aborts the request.”

Stays on your device. Nothing is uploaded.
Follow-up How do you cancel a fetch request?
Pass { signal: controller.signal } to fetch and call controller.abort(); then handle the AbortError.
Follow-up Does aborting a fetch stop the server's work?
No. It closes the connection on the client. The server may finish anyway, so writes must be safe to repeat or check for duplicates.
Follow-up Can you reuse an AbortController after abort()?
No. A signal aborts once and stays aborted; create a new controller for the next request.

Practice#

On your own

Make sleep abortable, then build fetchWithTimeout from two signals.

Done when

  • sleep rejects with signal.reason when aborted, and at once if already aborted.
  • The timer is cleared, so an aborted sleep never resolves later.
  • fetchWithTimeout aborts on the caller’s signal or after timeout ms, whichever comes first.
Your solution
Ctrl/⌘ + Enter

Remember#

Try to answer each card before you open it. Then add them to your reviews.

How do you cancel a fetch?

Pass an AbortController's signal to fetch, then call controller.abort().

What error does fetch reject with after abort()?

A DOMException named AbortError (or the reason you passed).

What error name does AbortSignal.timeout give?

TimeoutError.

What does AbortSignal.any([a, b]) return?

A signal that aborts when any of the given signals aborts.

Go deeper

Four layers. Stop at the one your interview needs; each ends with a quick check.

The core ideaEvery round

abort(reason) sets signal.aborted to true and signal.reason to your reason, or to an AbortError by default. In UI code, check error.name === "AbortError" and do nothing: a cancelled request is not a failure.

Check yourself: What does signal.reason hold after controller.abort()?

A DOMException named AbortError, unless you passed your own reason.

How it works underneathSenior rounds
To make your own function abortable: take a signal, listen for "abort" and undo your work there (clear timers, close sockets).
Call signal.throwIfAborted() at the start and between steps, so already-cancelled work stops at once Source: MDN: AbortSignal.
Check yourself: Why use { once: true } on the abort listener?

The signal aborts once, and the listener is removed after it runs.

Edge cases and trapsWhere people slip

AbortSignal.timeout(ms) aborts with a TimeoutError. AbortSignal.any([a, b]) aborts when any input does Source: DOM Standard: AbortSignal. Support for any: Chrome 116, Firefox 124, Safari 17.4, Node.js 20.3.0.
Listeners on long-lived signals add up; remove them when your work finishes.

Check yourself: Which static method combines several signals?

AbortSignal.any()

In productionStaff-level answers

In React, create the controller inside the effect and abort it in the cleanup: return () => controller.abort(). The cleanup runs when inputs change or the component unmounts. In Strict Mode development, where effects run twice, it cancels the first request.
Data libraries such as TanStack Query pass a signal to your fetch function for you.

Check yourself: What should a React effect's cleanup do with an in-flight fetch?

Call controller.abort(), so the old request stops and its result is ignored.

Finished the five steps? Mark it, and its cards join your reviews.

Last checked 2026-10-31. Spot a mistake? Use “Report an error” below — fixes are listed on the corrections page.

Interview questions this answers

  • How do you cancel a fetch request?