Many developers assume that if an HTTP request has an error status code like 404 or 500 then the Promise will automatically reject and flow into the catch block. It might surprise you, but that's wrong.
HTTP errors remain in the try block when using native fetch API unless you manually throw an error (or use something like Axios).
What actually happens when we get HTTP 404 from our backend?
The native fetch API is implemented in a way where it only rejects (and triggers your catch block) when there's a genuine network problem. For example, your internet cuts out or if someone misconfigures CORS.
And what about those HTTP status codes like the 4xx and 5xx? They will come back as resolved promises which won't land in the catch block. UNLESS you manually throw new Error.
// Here's what happens with native fetch
async function getFetchData() {
try {
const response = await fetch('https://api.example.com/nonexistent');
console.log('Response status:', response.status);
// You've got to manually check if everything's actually okay
if (!response.ok) {
// This line ensures it gets picked up by the catch block.
throw new Error(`HTTP error! Status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error('Caught in catch block:', error);
// By default only network failures or other errors end up here
}
}
If you work with native Fetch API, you have to specifically check that "response.ok" property to see if something went wrong.
Why This Confuses So Many Developers?
No wonder so many developers get confused when you consider that popular libraries like Axios work completely differently. Axios actually does what most of us expect would happen - HTTP errors automatically trigger the catch block.
How?
Axios is set in a way that the promise will get rejected if we get HTTP status like 400 or 500. You may read-up the Axios documentation to see how it works behind the scenes.
// With Axios, it works like you'd expect
import axios from 'axios';
async function getAxiosData() {
try {
// HTTP errors (4xx, 5xx) will immediately jump to catch
const response = await axios.get('https://api.example.com/nonexistent');
// This only executes for successful responses (2xx)
console.log('Data:', response.data);
return response.data;
} catch (error) {
// Both network errors AND HTTP errors land here
console.error('Caught in catch block:', error.message);
console.error('Status code:', error.response?.status);
}
}
Why Should You Care About This?
This isn't just some technical trivia - it actually matters for your day-to-day coding because:
Switching between libraries can introduce unexpected bugs if you're not aware of this difference
With fetch, forgetting to check response.ok can lead to silent failures that are a nightmare to debug
Legacy codebase might often use native fetch API rather than something like Axios
Top comments (0)