This chapter follows Async/await in JavaScript.
Once started, a Promise runs its executor unless the underlying API supports cancellation. AbortController and AbortSignal give a shared, composable way to say “stop”—used by fetch, many streams APIs, and custom libraries.
Basic pattern
const controller = new AbortController();const { signal } = controller;
const promise = fetch("/api/slow", { signal });
setTimeout(() => controller.abort(new Error("timeout")), 5_000);
try { const res = await promise;} catch (err) { if (err.name === "AbortError") { console.log("Request cancelled"); } else { throw err; }}abort(reason) sets signal.aborted and dispatches an abort event. fetch rejects with DOMException named AbortError when aborted.
Passing signals through your code
Functions that perform async work should accept an optional signal and register cleanup:
async function poll(url, { signal, intervalMs = 1000 } = {}) { while (!signal?.aborted) { const res = await fetch(url, { signal }); console.log(await res.text()); await new Promise((r) => setTimeout(r, intervalMs)); }}Check signal.aborted before starting expensive steps, and listen for abort to clear timers or close sockets.
Composing signals
AbortSignal.any(signals) (modern runtimes) aborts when any input signal aborts—useful for merging a user cancel button with a timeout controller.
AbortSignal.timeout(ms) aborts after a delay without manual setTimeout.
Limitations
Cancellation is cooperative: the task must observe the signal. A plain Promise wrapper around a callback-only API may keep running after you abort unless the underlying resource is released.
Observables often expose unsubscribe for the same reason—see the next chapter for when that model fits better.
Continue with Observables in JavaScript.