Request, Response, headers and bodies: fetch beyond the basics
The Request and Response objects behind fetch, why a body can be read only once (bodyUsed, clone), the credentials, mode, cache, redirect, keepalive and priority options, and a fetch wrapper with proper errors.
Comes up in: Web fundamentals, Coding
Learn#
Asha’s error logger does await res.json() to show the error, then her retry code calls res.text() on the same response and crashes with “body already used”. The bug report hides the real error.
A call to fetch gives you a response object as soon as the headers arrive. Its body is a stream. You read it with json(), text() or blob(), and a stream can be read once:
const res = new Response(JSON.stringify({ ok: true }), { status: 201, headers: { "Content-Type": "application/json" } });
const copy = res.clone();
console.log(res.status, res.ok, res.headers.get("content-type"));
console.log(await res.json(), res.bodyUsed);
try { await res.text(); } catch (e) { console.log(e.name); }
console.log(await copy.text());
const req = new Request("https://api.example/orders", { method: "POST", body: "{}" });
console.log(req.method, req.credentials, req.mode);Output (verified)
201 true application/json
{ ok: true } true
TypeError
{"ok":true}
POST same-origin corsA request object holds the URL, method, headers, body and options; fetch(request) sends it. clone() lets two readers share one body.
Network
- headers ✓ 201
- body: streaming …
State
empty
Console
empty
fetch resolves as soon as the headers arrive. The body is still a stream coming over the network.
Read the steps as text
- fetch resolves as soon as the headers arrive. The body is still a stream coming over the network.
- res.json() reads the stream to the end and parses it. bodyUsed becomes true.
- Calling res.text() now throws a TypeError: the stream was already consumed.
- res.clone(), made before the first read, splits the stream in two, so each copy can be read once.
Predict#
Predict first
fetch('/api/missing') gets a 404. What does the fetch promise do?
The fetch call rejects only on network failures, such as being offline, DNS or CORS errors. Any HTTP response, even 404 or 500, resolves. You must check res.ok or res.status. Forgetting this is a very common bug.
Interview angle#
Say it like this — about 30 seconds
“The fetch call resolves with a Response once the headers arrive. The body is a stream that can be read only once. After that, bodyUsed is true, so I call clone first if two readers need it. The fetch call rejects only on network errors, so I always check res.ok. For cookies on a cross-origin request I set credentials to include. The server must then allow credentials for my exact origin. Other options control mode, cache, redirects, keepalive and priority.”
Follow-up Why can you read a response body only once?
res.clone() before reading if you need it twice.Follow-up How do you send cookies with a cross-origin fetch?
credentials: "include", and the server must reply with the exact origin and Access-Control-Allow-Credentials: true; SameSite must also allow it.Follow-up When do you use keepalive?
Practice#
Guided
Write a fetchJson wrapper that checks the status, reads the body once, and returns useful errors.
Done when
- An
Accept: application/jsonheader is added, keeping the caller’s headers. - 204 resolves
null; other ok responses resolve the parsed body. - Errors carry
statusand the parsed JSON body or the text.
Remember#
Try to answer each card before you open it. Then add them to your reviews.
Does fetch reject on a 404?
No. It resolves; check res.ok or res.status.
How do you read a response body twice?
Call res.clone() before the first read.
What is fetch's default credentials mode?
same-origin.
What does keepalive: true do?
Lets a small request finish after the page unloads.
Go deeper
Four layers. Stop at the one your interview needs; each ends with a quick check.
The core ideaEvery round
omit, same-origin (default) or include.mode is cors (default), same-origin (fail on cross-origin) or no-cors (an opaque response you cannot read).Check yourself: What does mode: 'no-cors' give you?
An opaque response you cannot read; only useful for caching or fire-and-forget.
How it works underneathSenior rounds
cache option controls the HTTP cache for one request. Use no-store to skip it, no-cache to revalidate, or force-cache to prefer a cached copy.redirect is follow (default), error or manual.Check yourself: What does cache: 'no-store' do on a fetch?
Skips the HTTP cache completely for that request.
Edge cases and trapsWhere people slip
keepalive: true lets a request outlive the page, like sendBeacon, but in-flight keepalive bodies are limited to 64 KB in total Source: Fetch Standard: keepalive.content-type before parsing.Check yourself: What is the size limit for keepalive requests?
64 KB in total across in-flight keepalive requests.
In productionStaff-level answers
priority: "high" or "low" hints how important a request is, such as low for prefetching Source: MDN: RequestInit.Check yourself: Which fetch option lowers the priority of a background request?
priority: 'low'.
Finished the five steps? Mark it, and its cards join your reviews.
Last checked 2026-11-01. Spot a mistake? Use “Report an error” below — fixes are listed on the corrections page.
Interview questions this answers
- Why can you read a response body only once?
- How do you send cookies with a cross-origin fetch?