A Practical Guide to the Fetch API
A Practical Guide to the Fetch API
fetch() replaced XMLHttpRequest as the standard way to make HTTP
requests in the browser. The basics are simple, but a few sharp edges trip
people up — especially around error handling.
The basic request
const response = await fetch('/api/posts');
const posts = await response.json();
console.log(posts);
Fetch doesn’t reject on HTTP errors
This is the most common gotcha: fetch() only rejects on a network failure
(DNS error, no connection, CORS block). A 404 or 500 response is still
a “successful” fetch as far as the promise is concerned. You have to check
response.ok yourself.
async function getPost(slug) {
const response = await fetch(`/api/posts/${slug}`);
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`);
}
return response.json();
}
Sending JSON
await fetch('/api/posts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: 'New Post', body: 'Hello!' }),
});
Cancelling a request
Search-as-you-type is a classic case where you fire a request, then the
user keeps typing and you no longer care about the response. AbortController
lets you cancel an in-flight fetch:
let controller;
async function search(query) {
controller?.abort();
controller = new AbortController();
const response = await fetch(`/api/search?q=${query}`, {
signal: controller.signal,
});
return response.json();
}
If a new search() call comes in before the previous one finishes, the old
request is aborted and its promise rejects with an AbortError — which you
usually just want to ignore:
try {
const results = await search(query);
render(results);
} catch (err) {
if (err.name !== 'AbortError') throw err;
}
Timeouts
Fetch has no built-in timeout option, but AbortSignal.timeout() covers
the common case in one line:
const response = await fetch('/api/slow-endpoint', {
signal: AbortSignal.timeout(5000),
});
Summary
- Always check
response.ok— fetch won’t do it for you. - Use
AbortControllerto cancel stale requests. - Use
AbortSignal.timeout()instead of hand-rolling a timeout withsetTimeout.
