Fetch API: The Modern Browser Interface to HTTP
fetch() did not change the communication model that AJAX introduced.
It is still about:
JavaScript -> HTTP request -> backend -> response -> JavaScript
What changed is the interface.
The simplest request
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.
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.
const response = await fetch("/missing");
If the server responds correctly with:
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:
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
await fetch("/api/users", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
name: "Alice"
})
});
The browser builds a request made of:
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
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:
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.
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:
cancel waiting != rollback server work
Fetch does not design your API
After fetch() the question is still open:
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.