fetch() starts a browser-managed HTTP request and returns a Promise for a Response. That one line can involve URL parsing, security checks, cache rules, DNS, TLS, redirects, response headers, and body decoding.
const response = await fetch("https://api.example.com/data");
const data = await response.json();
The API is small because the browser handles the network machinery around it.
A Request object is prepared
The browser first builds a Request from the URL and options.
const request = new Request("https://api.example.com/data", {
method: "GET",
headers: { Accept: "application/json" },
credentials: "same-origin",
cache: "default"
});
At this point the browser knows the method, headers, body, credentials mode, redirect mode, cache mode, and target URL. It has not necessarily sent bytes yet.
Some headers are controlled by the browser and cannot be set by page JavaScript. That protects connection behavior and prevents scripts from spoofing certain request metadata.
Policy checks happen before access
If a script on one origin requests data from another origin, CORS rules decide whether JavaScript can read the response.
For some cross-origin requests, the browser sends a preflight OPTIONS request first. The server must answer with headers such as Access-Control-Allow-Origin and Access-Control-Allow-Methods.
A CORS failure is not the same as a 404. The browser blocks JavaScript from reading the response and fetch() rejects with a network-style error.
The cache may answer first
Before the network is used, the browser checks cache rules.
Depending on the request and cached response headers, it may:
- reuse a fresh cached response
- revalidate with
ETagorLast-Modified - bypass the cache
- store the new response for later
A revalidation request can return 304 Not Modified, which tells the browser to use the cached body. That saves transfer bytes while still checking freshness.
The network path
If the request needs the network, the browser resolves the host and opens or reuses a connection.
The path can include:
- DNS lookup for the domain
- TCP or QUIC connection setup
- TLS negotiation for HTTPS
- HTTP request bytes sent to the server
- response headers returned
- response body streamed back
HTTP/2 and HTTP/3 can multiplex multiple requests over one connection, so later fetches to the same origin may avoid a new handshake.
The Response object
When response headers arrive, fetch() can resolve with a Response object.
const response = await fetch(url);
console.log(response.status);
console.log(response.ok);
console.log(response.headers.get("content-type"));
Important detail: HTTP error statuses do not reject the Promise.
const response = await fetch("/missing.json");
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
A 404 or 500 is still an HTTP response. Network failures, blocked CORS access, and aborted requests are the cases that reject.
The body is a stream
The response body is a stream of bytes. Helper methods read that stream and convert it:
await response.text();
await response.json();
await response.blob();
await response.arrayBuffer();
response.json() reads the body, decodes text, and parses JSON. If the body is not valid JSON, parsing throws after the response has arrived.
For large responses, you can read chunks from response.body:
const reader = response.body.getReader();
const decoder = new TextDecoder();
let output = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
output += decoder.decode(value, { stream: true });
}
Streaming fits long downloads, generated output, and interfaces that can render partial data.
Redirects and aborts
By default, fetch() follows redirects. You can change that with the redirect option, but most application code accepts the default behavior.
To cancel a request, pass an AbortSignal:
const controller = new AbortController();
const promise = fetch(url, { signal: controller.signal });
controller.abort();
Aborting matters when a user leaves a view, types a new search query, or cancels an upload. The request may already have reached the server, but the browser stops waiting for the result.
Watch it in DevTools
The Network panel shows the parts that fetch() hides:
- request URL and method
- status code
- request and response headers
- cache status
- timing breakdown
- transferred bytes
- response preview
Use it when a request fails. A CORS error, a 401, a wrong Content-Type, and invalid JSON can look similar in code but show different evidence in DevTools.
fetch() is a small API on top of a large browser pipeline. The reliable pattern is: make the request, check response.ok, inspect headers when needed, then parse the body once.





