AbortController is the standard way to cancel async operations in JavaScript. It's essential for preventing race conditions and memory leaks.
Basic Usage
const controller = new AbortController();
fetch("/api/data", { signal: controller.signal })
.then(res => res.json())
.then(data => console.log(data))
.catch(err => {
if (err.name === "AbortError") {
console.log("Request was cancelled");
} else {
console.error("Fetch failed:", err);
}
});
// Cancel the request
controller.abort();React: Cancel on Unmount
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
const controller = new AbortController();
fetch(`/api/users/${userId}`, { signal: controller.signal })
.then(res => res.json())
.then(setUser)
.catch(err => {
if (err.name !== "AbortError") console.error(err);
});
return () => controller.abort(); // Cleanup!
}, [userId]);
return user ? <div>{user.name}</div> : <Loading />;
}Race Condition Prevention
function SearchResults({ query }) {
const [results, setResults] = useState([]);
useEffect(() => {
const controller = new AbortController();
// If query changes rapidly, previous requests are cancelled
fetch(`/api/search?q=${query}`, { signal: controller.signal })
.then(res => res.json())
.then(setResults)
.catch(err => {
if (err.name !== "AbortError") console.error(err);
});
return () => controller.abort();
}, [query]); // Previous fetch cancelled when query changes
return <ul>{results.map(renderResult)}</ul>;
}Timeout Pattern
async function fetchWithTimeout(url, timeout = 5000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, { signal: controller.signal });
return await response.json();
} finally {
clearTimeout(timeoutId);
}
}
// Or use AbortSignal.timeout() (modern browsers)
const response = await fetch(url, {
signal: AbortSignal.timeout(5000)
});Combining Signals
// AbortSignal.any() — abort if ANY signal fires
const userCancel = new AbortController();
const response = await fetch(url, {
signal: AbortSignal.any([
userCancel.signal,
AbortSignal.timeout(10000)
])
});Beyond Fetch
AbortController works with any API that accepts AbortSignal: addEventListener, ReadableStream, and custom async operations.
// Cancellable event listener
element.addEventListener("scroll", handler, { signal: controller.signal });
controller.abort(); // Removes the listener!