Build a search-suggest input that cancels stale in-flight requests
Wire up a search box that fetches suggestions as the user types. On each input, request /suggests?term=<value> and render the results in a list below the field. Handle these cases: do not fetch on an empty/blank term; on an error, show nothing; and crucially, ensure a newer request's results always replace an older one's, even if the older request resolves last.
async function onInput(value) {
// your code here
}
Write the implementation.
Keep a module-level AbortController. On each input: trim() and bail if empty; abort the previous controller, create a new one, and fetch with its signal. Because aborting cancels the older in-flight request, a slow earlier response can no longer overwrite a newer one — the out-of-order race is gone. Wrap the fetch in try/catch, ignore AbortError, render nothing on other errors. Pair with a debounce.
- ✗Ignoring the request race, so a slow older response overwrites a newer one's suggestions
- ✗Letting
AbortErrorsurface as a real error and clearing the list when a request was deliberately cancelled - ✗Fetching on an empty/whitespace term instead of bailing out early
- →How does aborting the previous request prevent an out-of-order response from winning?
- →Why should
AbortErrorbe treated differently from a network error in the catch block?
Solution
AbortController cancels the previous request, removing the response-order race.
let controller = null;
async function onInput(value) {
const term = value.trim();
if (!term) {
render([]);
return;
}
controller?.abort();
controller = new AbortController();
try {
const res = await fetch(`/suggests?term=${encodeURIComponent(term)}`, {
signal: controller.signal,
});
const suggests = await res.json();
render(suggests);
} catch (e) {
if (e.name === 'AbortError') return; // cancelled on purpose — ignore
render([]); // real error — show nothing
}
}
How it works
Without cancellation a race is possible: the user types fast, several requests go out, and a slow earlier response arrives last — clobbering the suggestions for a fresher term. AbortController fixes this: on each input we abort the previous controller, and its fetch rejects with AbortError before ever reaching render.
In catch we distinguish AbortError (our own cancellation — just return) from real errors (render nothing). A blank term is filtered out before fetching. In practice this is combined with a debounce so a request is not sent on every keystroke. </content>