Authority lesson · 866 words
Promises, Fetch, Cancellation, and Race Control
A promise settles once, but user intent keeps changing
When a user types, changes filters, navigates, or resubmits, multiple requests can overlap. The network may return them out of order. If every response updates the interface, an older result can overwrite the newer choice. The defect is not that promises are unreliable; it is that the application did not define which operation owns the visible state. Give each request an identity or generation and apply a result only when it still represents current intent.
Keep request state explicit: idle, loading, successful, empty, failed, or cancelled. Do not infer loading from missing data. Separate previously displayed data from the request in progress so the interface can choose whether to retain it, mark it stale, or clear it. This makes accessibility messages and tests deterministic.
Cancellation is resource and intent control
AbortController lets an application signal that a fetch is no longer needed. Abort when a component is removed, a newer search supersedes the old one, or a deadline expires. Treat AbortError as a distinct outcome rather than showing a failure message. Cancellation may not stop the server after the request has arrived, so server operations still need idempotency and authorization.
Pass the signal through every adapter that performs work. Clean up timers and event listeners. Do not create one global controller for unrelated requests. Ownership should follow the feature instance or operation. If several requests form one operation, decide whether one cancellation stops all of them or only a branch.
Fetch success is not HTTP success
Fetch rejects for network failures, not for ordinary 4xx or 5xx responses. Check response status and parse content according to its type. Handle empty responses and invalid JSON. Bound body size where possible and avoid inserting untrusted text with innerHTML. Translate transport details into domain results such as validation_error, unauthorised, temporarily_unavailable, or invalid_response.
Use deadlines and retries carefully. Retrying a safe GET may be reasonable for selected temporary failures. Retrying a POST can duplicate work unless the server supports idempotency. Respect rate-limit guidance and add jitter. The browser should not create a retry storm when the service is overloaded.
Test ordering and accessibility
Write tests that resolve requests in reverse order, cancel during navigation, return 404 and 500 responses, provide malformed JSON, and simulate slow networks. Verify that stale results are ignored, controls are re-enabled, focus is not stolen, and status messages are announced appropriately. A spinner without accessible status can leave keyboard and screen-reader users uncertain whether anything happened.
Instrument request duration, cancellation, error category, and zero-result outcomes without capturing private query content unnecessarily. Operational evidence helps distinguish slow APIs, client race defects, and users refining a search. Keep analytics subordinate to privacy and the actual product decision.
Release review for promises fetch cancellation and race control should name an owner, evidence location, review date, and the exact condition that would trigger a different decision. That record keeps the lesson tied to operational responsibility rather than leaving it as general advice. Teams should revisit the choice after incidents, major dependency changes, new data classes, or a material increase in scale.
Release review for promises fetch cancellation and race control should name an owner, evidence location, review date, and the exact condition that would trigger a different decision. That record keeps the lesson tied to operational responsibility rather than leaving it as general advice. Teams should revisit the choice after incidents, major dependency changes, new data classes, or a material increase in scale.
Scenario
A search box shows results for the previous query
A visitor types “python”, then quickly changes to “python testing”. The second request returns first, but the slower first request later replaces the results. Store a request generation, abort the previous fetch, and verify the generation before rendering. The server still treats both searches as independent safe requests.
- Give the operation an owner
- Abort superseded work
- Ignore stale completions
- Preserve accessible loading and result status
Worked example
Implement latest-search-wins behaviour
- Increment a generation counter and abort the previous controller.
- Render a loading state tied to the current generation.
- Fetch with the new signal and check response.ok.
- Before applying data, confirm the generation is still current.
- Handle cancellation silently and render other failures with a recoverable message.
Checklist
Use this before acting
- Model request states explicitly
- Assign ownership or generation to overlapping work
- Abort superseded or unmounted requests
- Check HTTP status and content type
- Keep untrusted content out of unsafe HTML sinks
- Retry only safe or idempotent operations
- Test reverse ordering and cancellation
- Provide accessible status messages
Common mistakes
Failure patterns to avoid
- Assuming response order matches request order
- Showing cancellation as an error
- Treating every resolved fetch as success
- Retrying non-idempotent requests
- Using one controller for unrelated operations
- Replacing focused content unexpectedly
Practical exercise
Race-test one interactive feature
Choose autocomplete, filters, tabs, or navigation data. Add explicit states, cancellation, latest-intent protection, status announcements, and tests that resolve requests in every meaningful order.
Deliverable: A race-safe component with automated ordering and accessibility tests.
Sources and further reading
Primary and official references
Topic-specific takeaway
Reliable browser concurrency comes from explicit ownership, cancellation, stale-result protection, and accessible request states.