Asyncintermediate
The Fetch API
The modern, Promise-based browser API for making HTTP requests, replacing XMLHttpRequest for most use cases.
fetch makes HTTP requests and returns a promise. It replaced XMLHttpRequest for almost everything.
fetch is like ordering delivery: the promise resolving is the doorbell ringing — it doesn't mean the food is good or that you've opened the box yet.
Key Concepts
1
const res = await fetch('/api/users');
const data = await res.json();2
Two awaits, because the body arrives separately. The first promise resolves when the headers are in; the
body is still streaming. .json(), .text() and .blob() each return a promise for the parsed body.
.json().text().blob()
3
The trap that catches everyone: a 404 does not reject.
const res = await fetch('/missing');
// no error -- res.ok is false, res.status is 4044
fetch rejects only on a network failure — DNS, no connection, CORS. Any HTTP response, including 500, is a
successful fetch.
fetch
5
if (!res.ok) throw new Error(`HTTP ${res.status}`);HTTP ${res.status}
6
That check is required on every call, and omitting it means parsing an error page as JSON and failing with a
confusing message.
7
A POST needs three things.
await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(user)
});8
body must be a string, FormData, Blob or similar — not an object. Passing an object sends
[object Object], and the header must match what you actually sent.
bodyFormDataBlob[object Object]
9
With FormData, do not set Content-Type — the browser sets it including the multipart boundary, and
setting it by hand breaks the request.
FormDataContent-Type
10
Cancellation is AbortController.
const c = new AbortController();
fetch(url, { signal: c.signal });
c.abort(); // the promise rejects with an AbortErrorAbortController
11
That is also how a timeout is implemented properly, since Promise.race leaves the request running.
Promise.race
12
Credentials are not sent by default cross-origin.
credentials: 'same-origin' // the default
credentials: 'include' // send cookies cross-origin13
The body can be read once.
await res.json();
await res.text(); // TypeError: body already read14
res.clone() if you need it twice — logging the raw text and then parsing, for example.
res.clone()
15
What the interviewer is probing.1. "Does fetch reject on a 404?" Probing: the classic gotcha. Stalls: "Yes." Moves up: no
— it resolves, and only rejects on a network failure; you must check response.ok yourself.
16
2. "How do you cancel a fetch?" Probing: AbortController. Stalls: "You cannot." Moves up:
pass an AbortController signal and call abort, which is also how you implement a real timeout.
17
3. "Why can you only read the body once?" Probing: the stream. Stalls: "You can read it
repeatedly." Moves up: the body is a stream consumed on read; clone the response if you need it
twice.
18
4. "What does credentials: include do?" Probing: cookies and CORS. Stalls: "Adds a header."
Moves up: it sends cookies cross-origin, which also requires the server to allow credentials and
name a specific origin rather than a wildcard.