跪拜 Guibai
← All articles
Frontend

Race Conditions in JavaScript Are Not About Threads

By 陆枫Larry ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Every frontend developer hits the stale-response bug in search boxes, autocompletes, and form submissions. Recognizing it as a race condition — and knowing that `await` alone does not prevent it — prevents intermittent UI corruption that is notoriously hard to reproduce and debug.

Summary

A search box that fires a fetch on every keystroke can display the wrong results when an earlier, slower request overwrites a later, faster one. The bug is not about threads or simultaneous execution — JavaScript remains single-threaded — but about overlapping async tasks that all hold a reference to the same state. The core risk is captured by a simple triangle: multiple tasks, shared mutable state, and an uncontrollable completion order.

The fix starts with a business decision: should the latest request win, the first request win, or should tasks queue? Once that rule is clear, a version number or request ID can gate writes, and AbortController can cancel work that is no longer needed. Loading indicators, error states, and pagination are just as vulnerable as the data itself.

Debugging these bugs is hard because they depend on timing and rarely reproduce consistently. Deliberately randomizing network delays or throttling in DevTools surfaces hidden races. The deeper lesson is that unreliable network timing should never silently become a business rule — the program must produce the correct result regardless of which response arrives first.

Takeaways
JavaScript's single thread does not prevent race conditions; async tasks can overlap and complete in any order.
`await` pauses only the current async function, not the entire runtime, so multiple async calls can still interleave.
A race condition forms when multiple tasks share mutable state and their completion order is unpredictable.
Before choosing a fix, define the business rule: latest-wins, first-wins, or queued execution.
A simple incrementing request ID lets a completed task check whether it still has permission to write state.
AbortController cancels in-flight work that is no longer needed, complementing version-based gating.
Loading, error, and pagination states are shared state too and are equally susceptible to races.
Debouncing reduces request volume but does not prevent races between the requests that are still sent.
Centralizing write decisions — having tasks produce results and a single arbiter decide what to commit — makes async logic easier to reason about.
Race condition bugs depend on timing and often pass QA; randomizing delays or throttling the network in DevTools exposes them.
Conclusions

Many developers mistake `await` for a sequencing guarantee across separate async calls, when it only linearizes code within a single function invocation.

The phrase 'last returned wins' is a hidden business rule that replaces the intended rule 'last requested wins,' letting network latency silently override user intent.

Version-number gating and request cancellation solve different problems — correctness versus wasted work — and are most effective when used together.

Loading spinners that flicker or disappear too early are often a race condition, not a timing bug, and deserve the same systematic treatment as data races.

Concepts & terms
Race condition
A bug where the outcome depends on the unpredictable order in which concurrent or overlapping tasks complete, rather than on the intended sequence of operations.
Latest Wins
A race-resolution strategy where the most recently initiated task is allowed to update shared state, discarding results from older, still-in-flight tasks.
First Wins
A race-resolution strategy where only the first successfully completed task is allowed to write state; subsequent tasks are ignored, commonly used for idempotent operations like order submission.
AbortController
A browser API that provides an `AbortSignal` to cancel an ongoing `fetch` request or other abortable async operations, preventing wasted work from stale tasks.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗