What is AbortController?
AbortController - a browser Web API that creates an AbortSignal to cancel fetch requests before the response arrives.
Theory
TL;DR
- Think of it like a fire alarm: pull it and the fetch stops before the response reaches your component
- Main use: cancel API calls when a React component unmounts, avoiding "setState on unmounted component" warnings
controller.abort()dispatches an abort event synchronously, closing the TCP socket- Decision rule: use it when a request takes more than 500ms or the component might unmount first
- AbortError is not a network failure. Always check
err.name !== 'AbortError'before showing errors to users
Quick example
const controller = new AbortController();
fetch('/api/user', { signal: controller.signal })
.then(res => res.json())
.then(data => console.log(data))
.catch(err => {
if (err.name === 'AbortError') console.log('Request canceled');
else console.error(err);
});
controller.abort(); // Output: "Request canceled"Passing signal to fetch ties that request to the controller. Call abort() and the network layer closes the socket. No response processed, no state updated.
Key difference
Before AbortController, developers used manual timeouts to "cancel" requests. Timeouts guess duration upfront and waste bandwidth on fast responses that were never needed. AbortController cancels at exactly the right moment: when you decide, not when a timer fires.
When to use
- Component unmounts before the API responds: add AbortController to useEffect cleanup
- User types a new search term before the old request finishes: cancel the previous one
- Race condition between tab views loading stale data: abort the old fetch
- Skip it for requests under 100ms or for APIs that don't accept an AbortSignal (like setTimeout)
How it works internally
When you call new AbortController(), the browser allocates an AbortSignal with an aborted flag set to false. Calling controller.abort() flips that flag to true and fires an 'abort' event synchronously. The fetch API listens to that event and tells the C++ network layer to close the TCP socket immediately. Node.js 15+ has the same mechanism via the undici HTTP client. No polyfills needed for modern environments.
Common mistakes
Forgetting the cleanup return in useEffect
// Wrong: no abort on unmount
useEffect(() => {
const controller = new AbortController();
fetch('/api/data', { signal: controller.signal }).then(setData);
}, []);
// Missing return means fetch runs after unmount, React prints a warningFix: return () => controller.abort() from the effect. That one line is all the cleanup you need.
Reusing an aborted controller
const controller = new AbortController();
controller.abort();
fetch('/api/new', { signal: controller.signal }); // Fails immediatelyAn aborted signal stays aborted. The fetch throws AbortError before any network activity. I've seen this trip up seniors during rushed refactors. Always create new AbortController() per request.
Treating AbortError as a real failure
// Wrong: shows error toast on intentional cancel
fetch('/api').catch(err => showErrorToast(err));Fix: if (err.name !== 'AbortError') showErrorToast(err);
Assuming axios supports signal automatically
Axios v1.6+ accepts signal in config. Older versions ignore it with no warning. Check your version, or use native fetch for this pattern.
Real-world usage
- React Query: auto-passes
signalto your query function, aborts on component unmount - SWR: passes AbortSignal to the fetcher for stale-while-revalidate cancellations
- RTK Query:
createApiaborts in-flight queries when the component disposes - Express (Node.js):
res.setTimeout(5000, () => controller.abort())for hung connections AbortSignal.timeout(5000): static shorthand that auto-aborts after a set time, no manualabort()call needed
Follow-up questions
Q: What happens if you call abort() after the response already arrived?
A: Nothing. AbortController only closes the socket before the response is buffered. Once fetch has the response, aborting has no effect.
Q: What is the difference between AbortSignal.timeout() and AbortController?
A: AbortSignal.timeout(5000) auto-aborts after 5 seconds with no extra code. AbortController is a manual trigger: you call abort() when user action or component lifecycle says to. Use timeout for simple time limits, AbortController for everything else.
Q: What browser versions support AbortController?
A: Chrome 66+, Firefox 57+, Safari 12.1+. Node.js 15+ natively. For older Node, the abort-controller npm package works.
Q: How do you cancel multiple parallel requests with one controller?
A: Pass the same signal to all fetch calls. One abort() cancels all of them. But that removes independent control. For separate cancellation, create one controller per request.
Examples
Basic: cancel on component unmount
import { useEffect, useState } from 'react';
function ProductDetails({ productId }) {
const [product, setProduct] = useState(null);
useEffect(() => {
const controller = new AbortController();
fetch(`/api/products/${productId}`, { signal: controller.signal })
.then(res => res.json())
.then(setProduct)
.catch(err => {
if (err.name !== 'AbortError') console.error(err);
});
return () => controller.abort(); // Runs on unmount or when productId changes
}, [productId]);
return product ? <h1>{product.name}</h1> : <p>Loading...</p>;
}When productId changes, React runs the cleanup before the next effect fires. That cleanup calls abort(), canceling the previous request. Only the latest response updates state.
Intermediate: live search with automatic cancellation
import { useEffect, useState } from 'react';
function UserSearch({ query }) {
const [users, setUsers] = useState([]);
useEffect(() => {
if (!query) return;
const controller = new AbortController();
fetch(`/api/users?q=${query}`, { signal: controller.signal })
.then(res => res.json())
.then(setUsers)
.catch(err => {
if (err.name !== 'AbortError') console.error('Search failed:', err);
});
return () => controller.abort(); // Cancel on next keystroke
}, [query]);
return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}
// No stale results, no "setState on unmounted component" warningEach query change triggers cleanup, which cancels the previous fetch. The new effect starts a fresh controller. Users see results for their latest input, not for some earlier keystroke.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.