fetch() Not Handling Network Errors – Detailed Explanation and Fixes
The fetch()
API in JavaScript is used to make network requests. However, many developers misunderstand how it handles network errors, which can lead to unhandled failures and unexpected behaviors in applications.
Understanding the Problem
Unlike XMLHttpRequest
, the fetch()
function does not reject the promise on HTTP errors like 404 Not Found
or 500 Internal Server Error
. Instead, it only rejects the promise when a network failure occurs, such as:
- The user is offline.
- The server is unreachable.
- There is a DNS failure.
- The request is blocked by CORS (Cross-Origin Resource Sharing) policy.
Because of this, developers often assume that fetch()
errors out for all bad responses, leading to logic issues.
Example of the Issue
Incorrect Handling of Errors
fetch("https://example.com/api/data")
.then(response => {
if (response.ok) {
return response.json();
}
// ❌ Developers often forget this part
throw new Error("HTTP error! Status: " + response.status);
})
.then(data => console.log(data))
.catch(error => console.error("Fetch error:", error));
What Happens Here?
- If the request succeeds but returns a
404
,500
, or any other non-2xx HTTP status, fetch does not reject the promise. - The
.then()
block still executes, but without proper handling of theresponse.ok
check, you might not realize there was an error. - The
.catch()
block will only execute if there’s a network failure, not when the response status is an error.
Correcting the Issue
To properly handle network and HTTP errors, always check response.ok
and manually throw an error.
✅ Proper Error Handling in Fetch
fetch("https://example.com/api/data")
.then(response => {
if (!response.ok) {
// Manually throw an error for HTTP status codes like 404 or 500
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json(); // Process the response body if OK
})
.then(data => console.log("Fetched Data:", data))
.catch(error => console.error("Fetch error:", error.message));
What’s Different Here?
- The
if (!response.ok)
condition ensures that if the response is not a successful HTTP status (e.g., 200 OK), we throw an error. - The
.catch()
block now correctly catches both:- Network failures (like no internet).
- HTTP errors (like 404 or 500).
Handling Fetch Timeouts (Avoiding Infinite Waiting)
The fetch()
API does not have built-in timeout support. If a request hangs indefinitely, it will never be rejected unless explicitly handled.
✅ Adding a Timeout to Fetch
function fetchWithTimeout(url, timeout = 5000) {
return Promise.race([
fetch(url).then(response => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
}),
new Promise((_, reject) => setTimeout(() => reject(new Error("Request timeout")), timeout))
]);
}
fetchWithTimeout("https://example.com/api/data", 5000)
.then(data => console.log("Fetched Data:", data))
.catch(error => console.error("Fetch error:", error.message));
How It Works
Promise.race()
ensures the request is aborted if it takes longer thantimeout
milliseconds.- If the request times out, it rejects with
"Request timeout"
.
Handling Fetch Errors for Offline Mode
If a user is offline, fetch()
fails with a TypeError
.
✅ Detecting Offline Mode
fetch("https://example.com/api/data")
.then(response => {
if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);
return response.json();
})
.catch(error => {
if (!navigator.onLine) {
console.error("User is offline. Please check your connection.");
} else {
console.error("Fetch error:", error.message);
}
});
How This Helps
- If the user is offline, we explicitly log
"User is offline"
instead of a generic fetch error.
Handling JSON Parsing Errors
Even if the request succeeds, the server might return invalid JSON, causing .json()
to throw an error.
✅ Safe JSON Parsing
fetch("https://example.com/api/data")
.then(response => {
if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);
return response.text(); // Read response as text first
})
.then(text => {
try {
return JSON.parse(text); // Try parsing manually
} catch {
throw new Error("Invalid JSON response");
}
})
.then(data => console.log("Fetched Data:", data))
.catch(error => console.error("Fetch error:", error.message));
Why This Works
- It first retrieves the response as
text()
, then manually attemptsJSON.parse()
, handling cases where the response is not valid JSON.
Handling CORS Errors
CORS (Cross-Origin Resource Sharing) errors occur when:
- A request is blocked due to missing CORS headers on the server.
- A request is made to a different domain without proper CORS configuration.
Identifying a CORS Error
- The browser blocks the request and does not give details (for security reasons).
fetch()
fails silently without a specific error message.- The error message in the console usually says something like:
Access to fetch at 'https://example.com/api' from origin 'https://your-site.com' has been blocked by CORS policy
✅ Fixing CORS Errors
- If you control the API, add the correct CORS headers:
Access-Control-Allow-Origin: *
or for specific domains:Access-Control-Allow-Origin: https://your-site.com
- If you don’t control the API, use a CORS proxy (only for testing).
fetch("https://cors-anywhere.herokuapp.com/https://example.com/api/data")
Summary – Best Practices for Handling Fetch Errors
Issue | Solution |
---|---|
HTTP errors (404, 500) | Check response.ok , throw error manually |
Network failures | Use .catch(error => console.error(error)) |
Timeouts | Use Promise.race() with setTimeout() |
Offline mode detection | Check navigator.onLine |
JSON parsing errors | Use text() before JSON.parse() |
CORS errors | Ensure proper API headers or use a proxy |
By following these best practices, you can make your fetch()
requests more robust, reliable, and error-proof.
Final Thoughts
Handling errors properly in fetch()
ensures your web applications provide better user experience, avoid silent failures, and improve debugging. Always anticipate network failures, bad responses, and timeout issues to make your app more resilient!