跪拜 Guibai
← All articles
Frontend · JavaScript · Programmer

Six Race-Condition Fixes, One Real Winner: AbortController

By kyriewen ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Stale search results from out-of-order responses corrupt almost every search, filter, and list UI, and the two most popular fixes make it worse or hide it entirely. AbortController now works everywhere, so the correct defense is a few lines instead of an architecture change.

Summary

A benchmark on a local search API with deliberately out-of-order response times pitted six race-condition defenses against two typing speeds, 30 Node rounds plus 10 Chrome rounds each. The verdict: loading flags and debounce, the two most common fixes, both leave stale results on screen at normal typing speeds. A bare `if (loading) return` guard silently swallows new input entirely, failing 30 of 30 fast-typing rounds with no visible error. A 300ms debounce is flawless at fast typing but lets every request through at normal 400ms intervals, showing old data half the time.

Sequence-number guards and latest-value comparison keep the UI correct in all 60 rounds, yet the server still computes all three requests; two-thirds of that work is discarded. AbortController cancels stale requests mid-flight instead of letting them finish and verifying afterward. Server compute drops from three runs to one on average, and the UI stays correct in every round.

The combination that holds up is debounce for request frequency plus AbortController for correctness. TanStack Query and SWR already implement this internally, and axios's deprecated CancelToken points at the same signal API as its replacement.

Takeaways
— A bare request flow shows stale data in 100% of fast-typing rounds and 47% of normal-typing rounds in a 30-round benchmark.
— An `if (loading) return` guard blocks new input before it is sent; it failed 30 of 30 fast-typing rounds with no visible UI error.
— A 300ms debounce is flawless at fast typing but leaks at 400ms intervals, showing stale data in 50% of normal-typing rounds plus a 300ms delay tax.
— Sequence-number and latest-value guards keep the UI correct in all rounds but the server still computes every request (3 of 3), wasting two-thirds of the work.
— AbortController keeps the UI correct and cuts server computations from an average of 3 to 1 by cancelling stale requests mid-flight.
— The recommended pattern combines debounce for request frequency with AbortController for correctness.
— TanStack Query and SWR implement the abort-plus-guard strategy internally; TanStack Query's useQuery with a queryKey handles races out of the box.
— axios has officially deprecated CancelToken and recommends AbortController as its replacement.
— An AbortController needs a .catch() for AbortError and a cleanup abort on component unmount to prevent ghost responses from landing on unmounted components.
— React setState batching does not fix races; out-of-order happens at the network layer and must be handled at the request layer.
Conclusions

Loading flags and debounce both target request frequency and submission blocking, not response ordering, which is why they fail at exactly the problem they are believed to solve.

The debounce failure at normal typing speed shows the danger of betting correctness on user cadence: any pause between keystrokes reopens the same race window as no protection at all.

Verifying a completed stale response versus cancelling it mid-flight produces identical UIs but very different server bills; the wasted two-thirds of compute multiplies across per-key autocomplete QPS and mobile data.

Short words being slower than long words, plus random jitter, mirrors real search backends; treating that as an edge case rather than the norm is how stale data ships.

Concepts & terms
Request race condition
A frontend bug where responses arrive out of order and the last to land overwrites the newest result, so a request sent earlier can display over a newer one.
AbortController
A Web API that cancels an in-flight fetch via a signal; the browser tears down the connection early so the server stops computing.
Loading flag guard
A boolean that blocks new requests while one is in flight; it prevents duplicate submissions but does nothing to order responses.
Debounce
A timer that delays a request until a quiet period passes, merging rapid keystrokes; it controls frequency, not correctness.
Sequence-number guard
Each request gets a monotonically increasing number; a response is discarded unless its number is still the latest, after the request has already run to completion.
From the discussion
Featured comments
我是李工啊

The experimental design is solid. One pitfall I've stumbled into: sequence-number guarding and AbortController are best used together. AbortController handles eliminating unnecessary requests, while sequence-number/latest-value comparison catches responses that have already arrived but are stale (e.g., cases where some libraries don't support cancellation). Also, the loading-flag swallowing input problem is essentially treating "preventing duplicate submission" and "getting the latest result" as the same thing; the former fits submit-type operations, and the latter fits search-type scenarios.

kyriewen

Like

See top comments, translated →
Source: juejin.cn ↗ Google Translate ↗ Backup ↗