Canceling AI Requests
Cancel in-flight AI requests with AbortController when the user navigates away or starts a new query.
TL;DR
- Create an
AbortControllerand pass itssignalto fetch. - Call
controller.abort()to cancel an in-flight request. - Abort the old request whenever a
newRequeststarts.
Set Up an AbortController
Create ItMake a controller for each request you start.
const controller = new AbortController();Pass the SignalHand the signal to fetch so it can cancel.
fetch(url, { signal: controller.signal });Abort ItCall abort to stop the request at once.
controller.abort();Cancel on Unmount
Cleanup FunctionReturn a cleanup that aborts the request.
useEffect(() => {
const c = new AbortController();
run(c.signal);
return () => c.abort();
}, []);No Stale setStateAborting stops updates after unmount.
// setState never runs post-abortFresh Each RunRecreate the controller when deps change.
// new controller per effect runCancel Stale Requests
Keep a RefTrack the current request's controller.
const ref = useRef<AbortController>();Abort the OldCancel the previous before starting anew.
ref.current?.abort();
ref.current = new AbortController();Latest WinsOnly the newest send stays running.
// stale responses are droppedHandle the Abort
Catch AbortErrorAborting rejects the fetch promise.
catch (e) {
if (e.name === 'AbortError') return;
setError(e);
}Ignore CancelsA cancel is expected, not a failure.
// AbortError is normal, skip itStop the ServerA closed request lets the route stop early.
req.signal.addEventListener('abort', stop);Tips
- Store the current controller in a
refand abort it before starting the next request, so only the latest answer reaches state. - Abort in a
useEffectcleanup so leaving the page stops the request and never sets state on an unmounted component.
Warnings
- An aborted fetch throws an
AbortError; catch and ignore it, or a normal cancellation will look like a real failure. - Without cancellation, a slow earlier response can overwrite a newer one; this
raceConditionshows stale AI answers.
In Practice
A hook that aborts the prior AI request every time the user starts a new one.
- A ref holds the controller for the currently running request.
- Each ask aborts the previous controller before creating a new one.
- Only the latest request's result reaches state, so no stale answer wins.
- AbortError from the canceled request is caught and ignored.
function useAsk() {
const [text, setText] = useState('');
const ref = useRef<AbortController>();
const ask = async (q: string) => {
ref.current?.abort();
const c = new AbortController();
ref.current = c;
try {
const res = await fetch('/api/chat', {
method: 'POST',
body: JSON.stringify({ q }),
signal: c.signal,
});
setText((await res.json()).text);
} catch (e) {
if ((e as Error).name !== 'AbortError') throw e;
}
};
return { text, ask };
}FAQ
Create an AbortController, pass controller.signal to fetch, and call controller.abort() to stop it. Do this in a useEffect cleanup to cancel on unmount, or before starting a new request to drop the previous one.
A slow earlier request finished after a newer one and overwrote state. Abort the previous request before each new send, or tag each request and ignore any result that is not the latest. Cancellation prevents the race entirely.
Yes, when the server stops too. Aborting the fetch closes the connection, and a well-built route detects the closed request and stops generating, so you stop paying for tokens no one will ever see.