Cancelling Async Work with AbortController
Users navigate away, type a new search or close a dialog while a request is still running. AbortController lets you cancel that work instead of letting it finish uselessly or, worse, overwrite newer results.
Cancel a fetch
const controller = new AbortController();
fetch('/api/report', { signal: controller.signal })
.then((r) => r.json())
.then(render)
.catch((err) => {
if (err.name === 'AbortError') return; // cancelled on purpose
showError(err);
});
cancelButton.onclick = () => controller.abort();
Only the latest search wins
let current;
async function search(query) {
current?.abort(); // cancel the previous request
current = new AbortController();
try {
const res = await fetch(`/api/search?q=${encodeURIComponent(query)}`, { signal: current.signal });
renderResults(await res.json());
} catch (err) {
if (err.name !== 'AbortError') throw err;
}
}
Timeouts in one line
const res = await fetch(url, { signal: AbortSignal.timeout(5000) }); // TimeoutError after 5s
Combine signals
const signal = AbortSignal.any([userController.signal, AbortSignal.timeout(10_000)]);
await fetch(url, { signal }); // cancelled by the user OR after 10 seconds
Make your own functions cancellable
async function processAll(items, { signal } = {}) {
for (const item of items) {
signal?.throwIfAborted(); // stop between steps
await processItem(item);
}
}
function sleep(ms, { signal } = {}) {
return new Promise((resolve, reject) => {
const id = setTimeout(resolve, ms);
signal?.addEventListener('abort', () => {
clearTimeout(id);
reject(signal.reason);
}, { once: true });
});
}
In React effects
useEffect(() => {
const controller = new AbortController();
fetch(`/api/users/${id}`, { signal: controller.signal }).then(...);
return () => controller.abort(); // runs when id changes or the component unmounts
}, [id]);
Event listeners accept a signal too: el.addEventListener('click', fn, { signal }) removes the listener when the signal aborts.
Was this article helpful?
Comments
No comments yet. Have a question or a tip? Be the first to share it.