Загрузка…
Загрузка…
Canonical pattern: abort previous on every keystroke; distinguish AbortError from real failures; never consume body twice; fetch не throws на 500.
Cancel the previous request on every new keystroke — the canonical pattern:
let controller; // holds the in-flight request's controller
async function search(query) {
controller?.abort(); // cancel the PREVIOUS request (kills stale results)
controller = new AbortController();
try {
const res = await fetch(`/api/search?q=${encodeURIComponent(query)}`, {
signal: controller.signal, // wire cancellation in
});
if (!res.ok) throw new Error(`HTTP ${res.status}`); // fetch won't do this for you
return await res.json();
} catch (err) {
if (err.name === 'AbortError') return; // expected — a newer request superseded this
throw err; // a REAL failure — surface it
}
}// Timeout without leaking a hanging request. AbortSignal.timeout() (modern) is cleanest:
const res = await fetch(url, { signal: AbortSignal.timeout(5000) }); // aborts after 5s
// Combine a user-cancel signal with a timeout signal:
const signal = AbortSignal.any([controller.signal, AbortSignal.timeout(5000)]);
await fetch(url, { signal });// ❌ common bug: consuming the body twice
const data = await res.json();
const text = await res.text(); // throws: body already read
// ❌ another: assuming fetch throws on 500 — it does NOT
await fetch('/api').then(r => r.json()); // parses the error page, hides the failureAbortController + res.ok + single body read — три обязательных habit для SPA fetch.
