fetch()———▶internals
Live — the round trip in one line
fetch()
WebIDL boundary
network service
socket / wire
Response → .then()

00 Explorable explanation · with Chromium internals

From fetch()
to the wire & back.

A single call crosses the JavaScript engine, a one-way language boundary, three operating-system processes, the security gates, and the socket — then makes the whole trip in reverse. Press play, click any node, and open “under the hood” in any detail for the real Chromium classes and the spec they implement.

▶ Round-trip · stack → task → microtask → stack
00 / 13
FRAME 00 · —

Press play to trace the call

Each frame highlights as the request token travels its lane. Amber lanes are owned by your JavaScript; teal lanes are owned by the browser engine.

JS-owned (your code) Engine-owned (C++) Reject path

01 The mental model

JavaScript describes what. The browser decides how.

Everything below is a consequence of one split. fetch() does not send bytes — it hands the browser a description of a request, returns a Promise immediately, and lets the engine do the real work on the far side of a hard boundary. The Web platform reuses this exact shape everywhere:

the recurring pattern a JS wrapper is created early  →  the engine does the real work  →  a JS wrapper is injected back later.
DOM     document.createElement() → JS wrapper → C++ DOM node
Events  addEventListener()       → JS wrapper → C++ dispatch
Timers  setTimeout()             → JS handle  → OS timer
Fetch   fetch()                  → JS Request → C++ network stack

02 The cast & their roles

A small set of objects — knowing who owns what explains everything.

The crucial asymmetry: the Promise and Request are born synchronously and early; the Response is born late, only after the network and security layers have run.

Object / layerRoleOwnerChromium class
PromiseHandle returned so your code can react laterJSScriptPromise / v8::Promise
RequestJS-facing description of the outgoing callJSblink::Request
HeadersValidated, normalized key/value mapJSblink::Headers
internal requestThe cross-process request structC++network::ResourceRequest
networking stackCORS, cookies, cache, socketsC++network::URLLoader → net::URLRequest
body streamBytes, possibly still arrivingC++mojo data pipe → BytesConsumer
ResponseJS wrapper around status, headers, bodyJSblink::Response
transportTCP text (1.1) or HPACK frames (2)OSHttpStreamParser / socket pool

03 Layers & points of interaction

Every boundary is a one-way copy — not shared memory.

This is precisely why JS cannot forge transport data. Toggle the overlay to dim everything the engine owns and see exactly where your code's reach ends. Click any layer for detail, then expand under the hood.

▣ The vertical stack

04 The process model · where the request physically lives

Your fetch leaves the renderer process entirely.

The biggest thing the simple “JS → C++” story hides: a fetch crosses three operating-system processes, connected by mojo IPC pipes. The renderer is sandboxed and owns no sockets. The browser process is the privileged broker. The actual networking — and, critically, the CORS enforcement — happens in a separate network service process. Click any box for the classes involved.

⬡ Renderer · Browser · Network service
the security punchline CORS is enforced in the network service, not the renderer — because under Site Isolation a compromised renderer must not be trusted to police its own cross-origin reads.

05 Outbound path

From your call stack to bytes on the wire.

fetch("https://api.example.com/users", {
  method: "POST",
  headers: {
    "Authorization": "Bearer token123",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ name: "Alex" })
});

Eight frames carry the request out. Each card opens the same deep-dive the hero nodes do — with a Chromium + spec tier inside.

06 CORS & preflight

The browser's veto — fired before a single byte of your request leaves.

Preflight guards against credential leakage, cross-site request forgery, and silent privilege escalation. Because Authorization is non-simple, the engine must ask permission first. Click a node to expand the gate.

⟂ The preflight gate
the key consequence fetch() returns its Promise before it even knows whether the request is allowed to exist.

07 Return path

Bytes become a Response, then a Promise resolution, then a value.

Five frames bring the response home. Notice the Response is born before the body finishes downloading — the body is a stream fed across a mojo data pipe.

08 The two-stage await

One promise for the headers, another for the body.

lock this in fetch() resolves when the browser can build a Response — usually once response headers arrive. It does not mean the body has downloaded.
const response = await fetch(url);      // 1) waits for headers + Response shell
const data     = await response.json();  // 2) waits for the body stream to drain & parse

The first await gets the metadata shell — status and headers. The second drains and parses the body stream. A slow body, a hung connection, or a manual response.body read all live in stage two; the Response object already exists by then.

09 What JS can read

CORS controls visibility, not just sending.

After a cross-origin response, the engine filters what response.headers will expose. This is also why DevTools ≠ JavaScript: DevTools reads the network-service state; your code reads a filtered wrapper view.

TierHeadersRule
alwaysCache-Control · Content-Type · Content-Language · Expires · Last-Modified · PragmaCORS-safelisted, low risk
opt-inanything named in Access-Control-Expose-Headersserver must allow it
neverSet-Cookie · Cookie · internal transport headerscredential boundary, stripped in the network service

10 1.1 vs 2 — same API, different wire

The abstraction boundary hides the framing entirely.

Your JS headers become abstract metadata, then protocol-specific framing chosen by the network service. You never see it. Flip the switch to morph the same headers between text and HPACK frames.

⇄ Header framing

11 Why this architecture exists

Opacity, on purpose.

PropertyWhat the boundary buys you
SecurityA sandboxed renderer can't forge Host/Cookie or read credential headers; CORS is enforced out-of-process
ConsistencyThe browser, not your script, enforces the HTTP & Fetch specs
PerformanceNetworking runs native, off the JS thread, in its own process
Evolvability1.1 → 2 → 3 changes the wire without changing fetch()
conclusion Headers are not strings you send — they are policies the browser enforces.
Back to JIT-Atlas