fetch not handling network errors

Loading

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?

  1. If the request succeeds but returns a 404, 500, or any other non-2xx HTTP status, fetch does not reject the promise.
  2. The .then() block still executes, but without proper handling of the response.ok check, you might not realize there was an error.
  3. 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 than timeout 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 attempts JSON.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

  1. 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
  2. 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

IssueSolution
HTTP errors (404, 500)Check response.ok, throw error manually
Network failuresUse .catch(error => console.error(error))
TimeoutsUse Promise.race() with setTimeout()
Offline mode detectionCheck navigator.onLine
JSON parsing errorsUse text() before JSON.parse()
CORS errorsEnsure 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!

Leave a Reply

Your email address will not be published. Required fields are marked *