I used to think that if a fetch() request failed, the catch block would handle it.

Something like this:

try { const response = await fetch("/api/users/123"); const user = await response.json(); console.log(user); } catch (error) { console.error("Request failed:", error); } 

But there's a detail here.

If the server returns a 404 or 500, fetch() doesn't reject the promise.

You still get a response object.

So this code:

const response = await fetch("/api/missing"); console.log(response.status); // 404 console.log(response.ok); // false 

works normally, assuming the server actually responds with a 404.

The catch block won't run just because the status code indicates an error.

To handle that, you need to check response.ok yourself.

async function getUser(id) { const response = await fetch(`/api/users/${id}`); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } return response.json(); } 

Now you can catch HTTP errors too:

try { const user = await getUser(123); console.log(user); } catch (error) { console.error(error); } 

response.ok is true for status codes from 200 to 299.

One more thing: calling response.json() doesn't mean the response contains valid JSON. If the server sends an HTML error page, for example, JSON parsing can throw a separate error.

So checking the HTTP status before parsing the body is usually a good idea.

It's a small distinction, but worth remembering when using the native Fetch API.