Why JavaScript's `fetch()` pretends that a 500 Server Error is a massive success.
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.
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();
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.
204 No Content status (e.g., after a DELETE request). If you check res.ok (it's true!) and then blindly call res.json(), it will crash because you can't parse an empty string as JSON. Always verify the API contract before parsing.