Free tools Windows power users keep installed
One-click scans. No signup required.
A 404 or 500 response does not normally make fetch() reject. It resolves with a Response, so check response.ok or response.status yourself and handle the response before parsing it as successful data. That is why a .catch() handler alone will not catch an HTTP error.
Why doesn’t fetch throw on a 404?
Fetch separates receiving an HTTP response from failing to make a request. If the server replies with a status such as 404 or 500, the promise normally fulfills with a Response. The ok property is true for status codes in the 200 range; status contains the numeric HTTP status.
As an Amazon Associate I earn from qualifying purchases.
A rejected Fetch promise is a different path, such as a network-level failure. Handle both paths deliberately: inspect the response for HTTP failures, and use try/catch for rejected requests and errors thrown while processing the response. See MDN’s Using the Fetch API.
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →Check the status before parsing successful data
If you call response.json() first, parsing may fail when the server’s error body is plain text, empty, or otherwise not valid JSON. That parse error can obscure the more useful fact that the server returned an HTTP failure. Read the body once, check the status, and parse it as JSON only when appropriate.
#1 Best Overall
export class HttpError extends Error {
constructor(
public readonly status: number,
public readonly statusText: string,
public readonly body: string,
) {
super(`HTTP ${status}: ${statusText}`);
this.name = "HttpError";
}
}
export async function fetchJson<T>(
input: RequestInfo | URL,
init?: RequestInit,
): Promise<T> {
const response = await fetch(input, init);
const body = await response.text();
if (!response.ok) {
throw new HttpError(response.status, response.statusText, body);
}
if (body.length === 0) {
throw new Error("Expected a JSON response body, but received an empty body.");
}
return JSON.parse(body) as T;
}
try {
const user = await fetchJson<{ id: string; name: string }>("/api/user");
console.log(user.name);
} catch (error: unknown) {
if (error instanceof HttpError) {
console.error("HTTP response failed:", error.status, error.body);
} else if (error instanceof Error) {
console.error(error.message);
} else {
console.error("Unexpected thrown value", error);
}
}
This helper treats an empty successful body as an error because it expects JSON. Change that behavior for endpoints that legitimately return no content. Also, as T is only a TypeScript assertion: it does not verify that the parsed JSON actually has the shape of T. Validate untrusted response data with an application schema or type guard before relying on its structure.
Read error bodies without assuming they are JSON
Response body readers such as text() and json() are asynchronous, and a response body should not be consumed twice. Reading with text() lets the helper retain plain-text or malformed error content for diagnostics. If an API documents structured JSON errors, inspect the response content type and parse accordingly; servers, proxies, and gateways do not all use the same error format. Preserve the HTTP status even if a body is missing or cannot be parsed. MDN documents the Response body-reading methods and status properties.
Rank #2
- TypeScript implements a superset of syntax for strictly typed development, facilitating deep static analysis and enhanced development environment integration. The compiler translates source into standard script formats, ensuring parity across any runtime.
- TypeScript is ideal for front-end developers, full-stack engineers, and software architects who build large-scale web applications. It serves those looking to improve code excellence, reduce bugs through static checking, and maintain complex projects more.
- Lightweight, Classic fit, Double-needle sleeve and bottom hem
Choose whether HTTP errors should throw
A custom error is useful when callers should use one exception path and need to branch on status or inspect the response body. The HttpError example above preserves those details and lets callers distinguish an HTTP response failure with instanceof.
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →If an HTTP error is expected control flow, return a discriminated result instead, so callers must handle success and failure explicitly without throwing:
type FetchResult<T> =
| { ok: true; data: T }
| { ok: false; status: number; body: string };
Whichever style you choose, keep the status and useful body details available to the code that decides what to do next.
Use TypeScript’s catch variable safely
TypeScript does not change Fetch’s runtime behavior; it affects how values are typed in your code. With useUnknownInCatchVariables enabled—which is part of the strict family of options—catch variables are unknown. Narrow them before reading properties such as message, for example with error instanceof Error or a check for your own HttpError class. The TypeScript 4.4 release notes document this behavior.
Do not retry every non-2xx response automatically
A non-2xx status alone does not establish that a retry is safe or useful. Decide based on the endpoint’s semantics, status, request method, idempotency, and any server guidance. A received HTTP error response and a rejected Fetch promise are distinct conditions; neither implies a universal retry policy.
Quick Recap
Best Value
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




