HTTP Status Ignored (fetch)

Why JavaScript's `fetch()` pretends that a 500 Server Error is a massive success.

The idea

When you use JavaScript's built-in fetch() API to make a network request, it returns a Promise. Most developers assume that if the server returns a 404 Not Found or a 500 Internal Server Error, the fetch() Promise will reject (throw an error) and jump into the catch block. It does not. fetch() only rejects if there is a network failure (like the DNS failing or the WiFi disconnecting). If the server successfully responds with a 500 Error, fetch() resolves successfully! If you blindly parse the response as JSON without checking the HTTP Status Code, your app will crash.

Step 1: Normal operation. Server returns a 200 OK with JSON data. fetch() resolves successfully.

How it works (res.ok)

Because a 500 Server Error is still a valid HTTP response, fetch() successfully returns a Response object. You must explicitly check the response.ok boolean (which is true for statuses 200-299) before you attempt to parse the body as JSON.

// BAD: Ignoring the status code
try {
    const res = await fetch("/api/data"); // Returns 500
    // CRASH: The server returned HTML saying "Server Error", 
    // so .json() throws a SyntaxError: Unexpected token <
    const data = await res.json(); 
} catch (e) {
    // We end up here, but with a misleading parsing error!
}

// GOOD: Explicitly checking res.ok
const res = await fetch("/api/data");
if (!res.ok) {
    // Now we can gracefully handle the 404 or 500
    throw new Error(`API failed with status: ${res.status}`);
}
const data = await res.json();

Cost

The cost of this design flaw in fetch() is thousands of hours of developer confusion. Libraries like axios exist entirely because they wrap fetch() and explicitly throw errors on 4xx and 5xx status codes, which is what 99% of developers intuitively expect.

Watch out for