Yo folks, I’m wiring up a search box in a vanilla JS page and trying to debounce the input event so I don’t spam the API, but sometimes it fetches using an older query even though the UI shows the latest text.
const input = document.querySelector('#q');
let timer;
input.addEventListener('input', (e) => {
const q = e.target.value;
clearTimeout(timer);
timer = setTimeout(async () => {
const res = await fetch(`/api/search?q=${encodeURIComponent(q)}`);
render(await res.json());
}, 250);
});
What’s the cleanest way to guarantee only the newest keystroke wins (including slow network responses) without making the code super tangled?
The stale value isn’t the debounce, it’s the response race. An older fetch can finish after the newer one and still call render().
Abort the previous request before starting the next one, and keep a tiny sequence check so only the latest response is allowed through:
const input = document.querySelector('#q');
let timer;
let controller;
let seq = 0;
input.addEventListener('input', (e) => {
const q = e.target.value;
clearTimeout(timer);
timer = setTimeout(async () => {
const mySeq = ++seq;
controller?.abort();
controller = new AbortController();
try {
const res = await fetch(`/api/search?q=${encodeURIComponent(q)}`, {
signal: controller.signal
});
const data = await res.json();
if (mySeq === seq) render(data);
} catch (err) {
if (err.name !== 'AbortError') throw err;
}
}, 250);
});
That keeps the UI from being overwritten by some slow response from three keystrokes ago.