🪟 Windows TippsThe Gemini desktop app is now available for Windows(11.09.2026 um 17:06 Uhr)
🪟 Windows TippsSetting up live captions stuck in Windows 11(14.09.2026 um 16:31 Uhr)
🕵️ SicherheitslückenBurn Out, Or Fade Away(14.09.2026 um 14:25 Uhr)
🪟 Windows TippsWindows 11's latest update broke my speakers, and I'm not alone(14.09.2026 um 18:54 Uhr)
🪟 Windows TippsThe Gemini desktop app is now available for Windows(11.09.2026 um 17:06 Uhr)
🪟 Windows TippsSetting up live captions stuck in Windows 11(14.09.2026 um 16:31 Uhr)
🕵️ SicherheitslückenBurn Out, Or Fade Away(14.09.2026 um 14:25 Uhr)
🪟 Windows TippsWindows 11's latest update broke my speakers, and I'm not alone(14.09.2026 um 18:54 Uhr)

🔧 Programmierung 🕛 vor 2 Monaten 5 Min Lesezeit
0

Your fetch() Is Still Running After the User Left

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht

When you fire a fetch() and the component that triggered it unmounts, the request keeps going. The server still processes it. When the response arrives, it calls back into whatever JavaScript it finds — a stale closure, a dead state setter, a global store that has already moved on. React's "Can't perform a state update on an unmounted component" warning is the polite version of this. The silent version is worse: results from an old query overwriting the current UI.



These aren't mysterious race conditions. They're the predictable result of starting async work and never telling it to stop.






The race condition hiding in every search box



The search input is the clearest example. The user types "reac", your debounce fires a request. Before it lands, they finish typing "react" and you fire another. Two requests, in flight at the same time, and no guarantee about which one finishes first.



If the "reac" request happens to be slower — network jitter, a cache miss, a heavier result set — it will land after "react" and overwrite the correct results with the wrong ones. The bug reproduces maybe one time in twenty on a local dev server, and consistently in production on a slow connection.



The fix isn't smarter debouncing. It's cancelling the previous request when a new one starts.






AbortController in plain terms



AbortController is a browser-native API for cancelling async work. You create a controller, pass its signal to fetch(), and call controller.abort() to cancel. If the response hasn't arrived yet, the fetch promise rejects with an AbortError.




CODE
const controller = new AbortController();

fetch('/api/search?q=react', { signal: controller.signal })
.then(res => res.json())
.then(data => setResults(data))
.catch(err => {
if (err.name === 'AbortError') return; // expected — not a real error
setError(err);
});

// Somewhere else, when we no longer need this request:
controller.abort();






Two things to internalize: signal is how the controller knows which request to cancel, and AbortError is intentional — catching and ignoring it is correct behavior, not a smell.






The React pattern: cancel in the cleanup function



useEffect's cleanup runs when the component unmounts and before the effect re-runs due to a dependency change. That makes it the natural place to cancel.




CODE
useEffect(() => {
const controller = new AbortController();

fetch(`/api/search?q=${query}`, { signal: controller.signal })
.then(res => res.json())
.then(data => setResults(data))
.catch(err => {
if (err.name !== 'AbortError') setError(err);
});

return () => controller.abort();
}, [query]);






When query changes, React runs the cleanup (aborting the in-flight request) before starting the next effect (firing a new one). The stale "reac" request is cancelled the moment "react" is typed — it will never call back into state.



When the component unmounts — navigation, modal close, conditional render — the cleanup fires and the pending request dies. No stale updates, no console warnings.






It's not just for fetch()



The signal property on an AbortController isn't coupled to fetch. Any API that accepts an AbortSignal can participate, and you can check signal.aborted in your own async loops:




CODE
async function processItems(items, signal) {
for (const item of items) {
if (signal.aborted) return; // bail out before the next iteration
await processOne(item);
}
}

const controller = new AbortController();
processItems(largeList, controller.signal);

// A cancel button, a timeout, a navigation — any of these can call:
controller.abort();






Check signal.aborted at each yield point. This pattern replaces global boolean flags and ad-hoc "is this still relevant?" tracking with a single, standard primitive that any caller can trigger.



For timeouts specifically, AbortSignal.timeout(ms) is cleaner than the manual approach:




CODE
// Auto-aborts after 5 seconds — no controller to hold, no setTimeout to clear
fetch('/api/slow-endpoint', { signal: AbortSignal.timeout(5000) })
.catch(err => {
if (err.name === 'TimeoutError') handleTimeout();
});






Note that timed-out requests throw TimeoutError, not AbortError — they're distinct cases, and it's useful to handle them differently.






What about React Query, SWR, or TanStack?



Data-fetching libraries handle this for you. React Query wires up an AbortController under the hood and cancels outgoing requests when a query key changes or a component unmounts. Understanding the pattern still matters: it's why the library behaves correctly, and you'll need the manual version any time you write custom fetch logic outside the library's boundaries.






The takeaway



Async code doesn't stop running because you stopped caring about it. A fetch() in flight is a real resource — a server-side computation, a response that will arrive and call something — and if nothing cancels it, it will complete against whatever state it finds.



AbortController is the correct primitive for this. The cost is three lines: create a controller, pass signal to fetch, return controller.abort from the cleanup. The return is eliminating an entire class of subtle, non-deterministic bugs.



If you've ever filed a race condition as "can't reliably reproduce," there's a good chance the unfiled fix is a missing controller.abort().






Thanks for reading! Let's stay connected:



Vollständiger Original-Bericht
Ausführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
↗ Original-Artikel auf dev.to lesen
Wie bewertest du diesen Beitrag?
1 Klick Feedback
Teilen mit Netzwerk & Team:

Community-Analysen & Experten-Meinungen 0

Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
Community Pulse: Relevanz-Einschätzung
1 Klick Experten-Votum
🔴 Akute Relevanz 0%
🟡 In Evaluierung 0%
🟢 Keine Auswirkung 0%
Spannende Innovation 0%
Verwandte Story-Cluster & Quellen (Vektor-KI)
Port 8095 Engine
1 Quelle
The Gemini desktop app is now available for Windows
1 Quelle
Setting up live captions stuck in Windows 11
1 Quelle
Burn Out, Or Fade Away
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Your fetch() Is Still Running After the User Left

Thematisch verwandte Begriffe: Your, fetch, Still, Running · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...