Hint
fetch() only rejects on network failure β HTTP 4xx/5xx must be checked via response.ok
The key gotcha: fetch() only rejects on network failure (no connection, DNS fail). HTTP errors like 404 or 500 are "successful" responses!
// β Wrong β HTTP errors silently "succeed"
const data = await fetch('/api').then(r => r.json());
// 404 or 500 still "resolves" β you get error HTML as data
// β
Correct β check response.ok
async function apiFetch(url, options = {}) {
const res = await fetch(url, options);
if (!res.ok) {
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
}
return res.json();
}
// POST with JSON
await apiFetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Alice' }),
});
// With timeout (AbortController)
async function fetchWithTimeout(url, ms = 5000) {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), ms);
try {
return await fetch(url, { signal: controller.signal });
} catch (err) {
if (err.name === 'AbortError') throw new Error('Request timed out');
throw err;
} finally {
clearTimeout(id);
}
}