Fetch API
debt(d7/e1/b3/t9)
Closest to 'only careful code review or runtime testing' (d7). The detection_hints list eslint and semgrep as tools, with a pattern matching 'await fetch( not followed by response.ok check' — this is a specialist/configured lint rule, not a default out-of-the-box catch. Most developers won't have this rule enabled by default, and the bug silently passes in production when servers return 4xx/5xx, so it sits closer to d7 than d5.
Closest to 'one-line patch or single-call swap' (e1). The quick_fix is explicit: add a single `if (!res.ok) throw new Error(...)` line immediately after each `await fetch()`. This is a minimal, surgical fix per call site.
Closest to 'localised tax' (b3). The fetch pattern is used wherever HTTP calls are made, but each call site is independently fixable. It doesn't impose a cross-cutting architectural obligation; it's a localised discipline required per usage site rather than a system-wide structural choice.
Closest to 'catastrophic trap — the obvious way is always wrong' (t9). The misconception is canonical and severe: every developer reasonably expects a Promise to reject on HTTP errors (as axios does), but fetch resolves on 4xx/5xx. The 'obvious' error handling pattern — try/catch around await fetch — silently fails to catch server errors. This directly matches the t9 anchor: the intuitive approach is always wrong for error handling.
Also Known As
TL;DR
Explanation
fetch(url, options) returns a Promise that resolves to a Response object as soon as headers arrive — not after the body is fully received. This means a 404 or 500 does not reject the Promise; you must check response.ok or response.status explicitly. The body is a readable stream consumed by methods like response.json(), response.text(), or response.arrayBuffer(), each returning a Promise. The options object controls method, headers, body (string, FormData, Blob, ReadableStream), mode (cors, no-cors, same-origin), credentials (include cookies with 'include'), and signal (AbortController for cancellation). In Node.js, fetch has been native since v18. Common patterns include adding auth headers via a wrapper function, handling errors uniformly, and cancelling in-flight requests when a component unmounts (React) or a new search starts.
Watch Out
Common Misconception
Why It Matters
Common Mistakes
- Not checking response.ok — a 500 response resolves the Promise, so error handling is silently skipped.
- Calling response.json() without awaiting response first — json() returns a Promise that must also be awaited.
- Sending JSON without setting Content-Type: application/json — the server receives the body but cannot parse it.
- Not aborting fetch on component unmount — causes state updates on unmounted components and memory leaks in SPAs.
Code Examples
// Missing response.ok check — silent failure on 4xx/5xx:
async function getUser(id) {
const res = await fetch(`/api/users/${id}`);
return res.json(); // parses error body as if it were success
}
// Check ok, handle errors, support cancellation:
async function getUser(id, signal) {
const res = await fetch(`/api/users/${id}`, {
headers: { Accept: 'application/json' },
signal, // AbortController signal
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
}