Cancel in-flight fetches cleanly with AbortController
AbortController gives a standard way to cancel in-flight fetches with one signal. Learn how to use it for timeouts, cleanup on unmount, and batch cancellation, plus how to distinguish AbortError from real failures.
29 Apr 2026, 05:34 UTC

The concrete problem is a fetch that finishes after the user has moved on. A component unmounts, a route changes, or the user types a new query, but the previous request still resolves and tries to update state. The useful takeaway is that AbortController gives you a single, standard way to cancel in-flight work and stop those late updates.
Why fire-and-forget fetches hurt
fetch returns a promise that settles when the network responds. There is no built-in timeout and no way to tell the browser to stop waiting. Without cancellation you end up with wasted bandwidth, race conditions where an old response overwrites a newer one, and attempts to set state on an unmounted component.
AbortController solves this by pairing a controller with an AbortSignal. The controller can be aborted from anywhere, and any API that accepts a signal will react to that abort. The signal is a read-only object with an aborted boolean and an event you can listen to.
One signal, many requests
A single AbortSignal can be shared across multiple fetches. That makes batch cancellation practical: you create one controller for a logical unit of work, pass controller.signal to each fetch, and call controller.abort() once to cancel them all.
Cleanup patterns rely on this. In an effect teardown or a navigation guard you abort the controller so pending requests are rejected with a DOMException whose name is AbortError. The rejection is explicit, so you can ignore it intentionally instead of treating it as a network failure.
Important distinction: AbortError means you cancelled the request. A network error or a non-2xx HTTP response is different. Catching all rejections the same way masks real failures.
A reliable timeout without polling
Combining AbortController with setTimeout gives a clean request timeout. You do not need a manual flag or polling.
// Run in a browser console or a module with fetch available. No special permissions required.
const controller = new AbortController();
const timeoutMs = 5000;
const timer = setTimeout(() => {
controller.abort();
}, timeoutMs);
fetch('https://example.com/api/resource', { signal: controller.signal })
.then(res => {
clearTimeout(timer);
if (!res.ok) throw new Error('HTTP error');
return res.json();
})
.then(data => {
// use data
})
.catch(err => {
clearTimeout(timer);
if (err.name === 'AbortError') {
// request was cancelled by timeout or explicit abort
return;
}
// handle real network or application errors
console.error(err);
});
Placeholders: replace the URL with your endpoint and adjust timeoutMs. Expected check: after controller.abort() the signal.aborted property becomes true and the fetch promise rejects. Risk: aborting a shared signal cancels every request that uses it. If you need independent cancellation, create separate controllers.
What abort does not do
Client-side abort stops the browser from processing the response, but it does not guarantee the server stops work. The remote request may still complete and consume resources.
Support is broad in modern browsers and Node, but AbortSignal is not universal in older browsers and legacy Node environments. For broad compatibility you need feature detection or a polyfill.
Verification you can do manually. Create a new AbortController in the console, call abort(), and verify controller.signal.aborted is true. Start a fetch with a signal and abort after a short delay, confirming the promise rejects with a DOMException named AbortError. In a component test, trigger unmount while a fetch is pending and verify cleanup aborts the controller and no post-unmount state update occurs.
Use AbortController when you need explicit cancellation for navigation, input debouncing, or timeouts. Keep AbortError handling separate from real errors, avoid sharing a signal when independent lifetimes are required, and remember that abort is a client-side signal only.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.