Fetch resolves when response headers arrive, including responses with error status codes. A caller must inspect response.ok or status before decoding a success body. A network failure rejects the promise; cancellation through AbortController also rejects, but is an expected outcome when a newer search replaces an older one. The user interface should not show a stale result from an earlier request after a newer query starts. One controller per in-flight search lets the next request abort the previous one. A timeout is a policy decision, not proof that the server did no work; a mutation may have completed even when the client stopped waiting. The example is a browser module for a read-only case search.
Fetch requests: separate HTTP failure, transport failure, and cancellation
Case study
An analyst searches for pump, then quickly searches for valve. The valve request cancels the pump request and becomes the only result allowed to update the list. A 404 response produces a missing-result state instead of pretending JSON parsing failed. A 500 response is classified as an HTTP server error, while an offline failure has no response status. The code sets textContent for one summary field; it does not put a returned HTML fragment directly into the page. If the search endpoint changes state, cancellation and retries require a stronger server contract than this read-only example.
Working contract
let activeSearch;
let requestNumber = 0;
async function searchCases(term, statusNode) {
activeSearch?.abort();
const controller = new AbortController();
activeSearch = controller;
const owner = ++requestNumber;
const timeout = setTimeout(() => controller.abort(), 6000);
try {
const query = new URLSearchParams({ term });
const response = await fetch(`/cases?${query}`, { signal: controller.signal });
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const payload = await response.json();
if (owner === requestNumber) statusNode.textContent = `${payload.total} cases`;
} catch (error) {
if (error.name !== "AbortError" && owner === requestNumber) {
statusNode.textContent = "Search unavailable. Try again.";
}
} finally {
clearTimeout(timeout);
}
}Cost and tradeoffs
For each search, request and response transfer dominate local controller setup, which is O(1). Decoding a JSON payload of B bytes costs O(B) time and storage. Aborting an obsolete fetch may save browser work but does not guarantee that the server stopped processing. A new request can still race with state changes unless the UI checks which query owns the display. The helper here uses a monotonically increasing request number in addition to aborting, so only the latest invocation can publish its summary.
Common Mistakes
- Do not treat a resolved fetch promise as proof of HTTP success.
- Do not display an older response after a newer query owns the view.
- Do not report an intentional abort as a server outage.
- Do not retry a state-changing request merely because the browser timeout fired.
Continue through the stack
JavaScript Tutorial; HTML status region: announce a changed result without stealing focus; DOM events: enhance a working control without losing its baseline; HTTP requests: keep method, status, and body contracts separate.
Broader connections
Cursor Pagination for Changing Collections; Server-Sent Events or WebSocket for Live Views.
Failure trace
A reviewer searches pump, then valve. The older pump request finishes last and overwrites the newer valve results. A different path treats a received 404 as a network exception because code assumes fetch rejects on error status. Abort obsolete reads, track which query owns the display, and inspect response.ok before decoding a success representation. A timeout on a write remains uncertain; cancellation cannot prove the server did nothing.
Verification
- Delay the first search response behind the second and ensure it never updates the final view.
- Return 404 and 500 responses separately from a rejected network request; verify distinct UI states.
- Cancel a write after it reaches the server and verify the client does not promise that storage is unchanged.
Decision note
Aborting can save browser work, yet the server may still finish. For read-only searches, stale-result suppression is enough; for writes, pair retries with an idempotent server contract.
Further connections
Streaming and Large Data Interfaces; Incremental Response Framing and UTF-8; Stream Cancellation and Partial-Result Contract.
