Konrad Kowalski (rootsher)Principal Platform & Reliability Architect000011110110101110110000011001010001111001001000

Fetch API: The Modern Browser Interface to HTTP

date
category
Frontend
also in
Backend
reading
1 min / 290 words

fetch() did not change the communication model that AJAX introduced.

It is still about:

text
JavaScript -> HTTP request -> backend -> response -> JavaScript

What changed is the interface.

The simplest request

js
const response = await fetch("/api/users/42");
const user = await response.json();

The first await waits for the HTTP response.

The second consumes the body.

That separation matters, because Response represents the response while the body may be a stream.

js
const response = await fetch(url);

response.status;
response.headers;
response.body;

response.body is a ReadableStream.

A 404 is not a fetch() error

This is one of the more common mental mistakes.

js
const response = await fetch("/missing");

If the server responds correctly with:

http
HTTP/1.1 404 Not Found

the promise is fulfilled.

fetch() fails mainly when no response can be obtained at all, because of a transport problem or a platform policy.

So HTTP semantics have to be checked explicitly:

js
const response = await fetch(url);

if (!response.ok) {
  throw new Error(`HTTP ${response.status}`);
}

response.ok means a status in the 200-299 range.

A request is more than a URL

js
await fetch("/api/users", {
  method: "POST",
  headers: {
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    name: "Alice"
  })
});

The browser builds a request made of:

text
method
URL
headers
body
credentials
cache policy
redirect policy

fetch is an API for constructing and performing requests, not a function "for downloading JSON".

The body is consumable

js
const response = await fetch(url);

await response.json();
await response.text(); // problem

The body is not a freely readable field. It is a stream, and once consumed it is marked as used.

If two consumers are needed:

js
const copy = response.clone();

That follows directly from the streaming model of the Fetch API.

Cancellation

A request may stop being needed.

Example: the user keeps typing characters into a search box.

js
const controller = new AbortController();

fetch("/search?q=rea", {
  signal: controller.signal
});

controller.abort();

Aborting does not undo work the backend has already done.

It mainly means the client no longer wants to wait for the result.

That is an important boundary:

text
cancel waiting != rollback server work

Fetch does not design your API

After fetch() the question is still open:

js
fetch("/users/42")
fetch("/getUser?id=42")
fetch("/graphql")

Each of those endpoints can use HTTP correctly.

They differ in how they model operations and data.

fetch answers the question:

how do I perform a request?

It does not answer:

what should our API look like?

The first popular answer to the second question is REST.