AbortSignal combines cancellation requests for APIs that accept it. A controller supplies a manual stop; timeout() supplies a time limit; any() combines signals. Their usefulness depends on the receiving operation and on how the application handles completion and cleanup.

Manual stop and clock paths joining a signal gate

Conceptual illustration, not a runtime trace.

Give each operation an owner

Keep the controller with the code allowed to request cancellation, and pass its signal to the operation. MDN describes this communication boundary and the controller's ability to abort fetch, body consumption and streams. Controller and participating operations

As an illustrative design exercise, imagine a search panel with a Cancel button. Write down which request belongs to the current search, who owns the button handler, and what completion means for that panel. A successful response should not silently become the result of a different search. This is an application decision to specify, rather than a property to assume from the presence of a signal.

Read the reason without guessing intent

A reason may be a JavaScript value rather than an Error object. Before cancellation it is undefined. Omitting a reason, including supplying undefined to abort(), selects the default AbortError. Guard its shape before inspecting properties. Reason values Defaulting rule in the DOM source

For abortable Promise-based web-platform APIs, the DOM integration requirements include rejecting with the reason and rejecting immediately when the supplied signal is already aborted. This does not make every Promise cancellable. Required API integration

In the search-panel exercise, record the Cancel button action separately if the interface needs to distinguish it from other stops. An exception name alone is a poor substitute for that application record. Decide whether the message should say that the user stopped the search, that its limit was reached, or that it failed for another reason; do not force every caught value into one category.

Combine inputs and understand the clock

With any(), already-aborted inputs are examined in iterable order: the first such input supplies the combined reason. Otherwise, the first input to abort supplies the combined reason; subsequent aborts do not replace it. Combining does not abort the other inputs. Ordered inputs and propagation

The timeout uses active time. It can pause in a suspended worker or while a document is in the back-forward cache; that is not a claim about every background tab. Active-time boundary

For an illustrative review, ask what should happen when the panel returns from navigation and its result is no longer useful. A timeout number by itself does not answer that product question. Also review the order in which pre-aborted inputs are assembled; naming one input “user” does not give it automatic priority over an earlier aborted input.

Expert perspective: include the body and cleanup

Jake Archibald's 2017 Chrome explanation emphasizes the controller/signal split and shows that fetch cancellation includes reading its response body. Its historical browser statements are not a current compatibility matrix. Dated specialist design explanation

MDN's cleanup example retains the application listener until response.text() finishes, then removes it in finally. once: true only removes a listener if its event fires; successful or failed completion also needs a cleanup path. Removing that listener neither cancels a timeout source nor manages fetch's internal listeners. Application listener lifetime Timeout source lifetime

Use the example as a checklist for an operation's beginning, body processing and exit. Who stops updating the panel after it closes? Where is the application-owned listener removed? Which target browsers support the specific methods? Check those answers in the intended environment before treating this design sketch as working code. On 2026-10-12, the coordinator ran this standalone local wait() demo in Chrome on macOS at localhost. UTF-8 button labels rendered in all three locales; manual cancellation produced AbortError: signal is aborted without reason, and an untouched timeout produced TimeoutError: signal timed out. This check does not cover fetch/network cancellation, BFCache, or other browsers.

Runnable minimal DOM example

Save this HTML as a UTF-8 file and open it in a browser to see a button cancellation and a three-second limit combined into one signal. wait() is a demo operation written to honor the signal. A real API must receive it too, for example fetch(url, { signal }).

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>AbortSignal cancellation demo</title>
</head>
<body>
<button id="cancel">Cancel</button>
<pre id="result">Waiting for an 8-second task</pre>
<script>
const result = document.querySelector("#result");
const controller = new AbortController();
const signal = AbortSignal.any([
  controller.signal,
  AbortSignal.timeout(3000),
]);
document.querySelector("#cancel").addEventListener("click", () => controller.abort());

function wait(ms, signal) {
  return new Promise((resolve, reject) => {
    if (signal.aborted) return reject(signal.reason);
    const timer = setTimeout(() => { cleanup(); resolve("Done"); }, ms);
    const onAbort = () => { clearTimeout(timer); cleanup(); reject(signal.reason); };
    const cleanup = () => signal.removeEventListener("abort", onAbort);
    signal.addEventListener("abort", onAbort, { once: true });
  });
}

wait(8000, signal)
  .then(message => { result.textContent = message; })
  .catch(error => { result.textContent = `${error.name}: ${error.message}`; });
</script>
</body>
</html>

Expected outcome: Click within three seconds and the default cancellation reason, AbortError, is shown. Otherwise TimeoutError appears after about three seconds. The eight-second task makes both cancellation paths occur before normal completion. This local demo exercises signal and event handling; it sends no network request. Check that the target browser supports AbortSignal.any() and timeout() before using them.

Sources