2025/03/18
This article stems from one of my favourite interview questions: “Can you explain how a fetch request works?”. It’s not just about the specific subject of the question, but about how intentional the approach to the understanding of the inner workings of a technology is. I believe that understanding how things work is a key part of being a good technical professional, and being intentional about it is what makes the difference between a good and a great one.
Foreword: we’re gonna be using the term “browser” throughout this article, but only Chromium-based browsers (like Chrome, Edge, Opera, Brave, etc.) are covered. The concepts are the same for other browsers, but the implementation details may vary.
Let’s dive into the anatomy of a fetch request, and see how it works under the hood. We’ll start from a very simple request:
fetch("https://example.com");
We know fetch
is a global function in the window
object. So where does it
come from?
The fetch
function is defined in the Fetch API IDL.
The IDL (Interface Definition Language) is a language used to define the
interfaces and types used in a web API. The IDL for the Fetch API defines the
fetch
function, its parameters, and its return value.
Its contract is defined as such:
[Exposed=(Window,Worker), SecureContext]
partial interface WindowOrWorkerGlobalScope {
Promise<Response> fetch(RequestInfo input, optional RequestInit init);
}
The function lives in the renderer process of the browser. The renderer process is
one of the main processes in a browser, responsible for rendering web pages and,
most importantly in this case, executing JavaScript code. The renderer process
exposes fetch
function and, when called, it sends a message to the network
process, which handles the actual network requests and responses, via
IPC (Inter-Process Communication), implemented in the NetworkService
class
using Mojo:
an IPC library collection used in Chromium.
In Blink, the FetchManager
class will start the process of creating a fetch
request using the FetchManager::Fetch()
method.
Once a ResourceRequest (low-level representation of a network request) is
prepared, Blink
sends it via Mojo IPC to the Network Service, which is
responsible for handling network requests and responses, via the
network::mojom::NetworkContext
interface, used to create a
network::mojom::URLLoaderFactory
:
network::mojom::URLLoaderFactory::CreateLoaderAndStart(...)
The payload sent over will include every single bit of information regarding the request, such as: • Method • Headers • Body • Credentials • Referrer • Priority Etc..
The Network service is a separate process in the browser that handles all network requests.
The top-level network stack object is the URLRequestContext. The context has non-owning pointers to everything needed to create and issue a URLRequest. The context must outlive all requests that use it.
It passes a network::ResourceRequest
object and
network::mojom::URLLoaderClient
Mojo channel to the
network::mojom::URLLoaderFactory
, and tells it to create and start a
network::mojom::URLLoader
.
The URLLoader then creates a URLRequest
to drive the network request. That
job first checks the HTTP cache, and then creates a network transaction object,
if necessary, to actually fetch the data. That transaction tries to reuse a
connection if available. If none is available, it creates a new one. Once it has
established a connection, the HTTP request is dispatched, the response read and
parsed, and the result returned back up the stack and sent over to the caller.
The steps to load a resource are as follows:
URLLoader
instanceURLRequest
instanceAs the response comes back, the Network service will stream it back to the renderer
process via Mojo IPC. Blink will waste no time accumulating the response, and
wrap it in a ReadableStream
object, which is a standard web API that allows you to read
data from a stream of bytes.
The ReadableStream
object is then passed to the Response
constructor, which
is responsible for creating a Response
object from the stream. The Response
object is a standard web API that represents the response to a network request.
After the hard work is done, Chrome might cache the response if its headers indicate that it should. It will then update performance metrics and notify