AJAX - AJAX Error Handling

When you make an AJAX call, several things can go wrong:

  • Network issues (no internet, server unreachable).

  • HTTP errors (404 Not Found, 500 Internal Server Error).

  • Response errors (malformed JSON, unexpected data).

  • Timeouts or aborted requests.

You need to handle these gracefully to avoid breaking your app.


1) Error Types in AJAX

  1. Network errors

    • Server unreachable, DNS failure, offline mode.

    • Example: fetch("http://wrong.url") → rejected promise.

  2. HTTP status errors

    • Server responds, but with an error code (404, 401, 500, etc.).

    • Browser treats it as a valid response → you must check .ok or status.

  3. Response parsing errors

    • Example: calling .json() on a non-JSON response.

  4. Timeouts / Aborts

    • Request took too long.

    • Request was manually aborted by AbortController or user action.


2) Error Handling with fetch

fetch("/api/data")
  .then(response => {
    if (!response.ok) {  // status not in 200-299
      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    }
    return response.json();
  })
  .then(data => {
    console.log("Data:", data);
  })
  .catch(error => {
    console.error("Fetch error:", error.message);
    // Handle: show message to user, retry, fallback, etc.
  });

Notes

  • .catch() handles network errors and exceptions in .then().

  • It does not auto-handle HTTP errors — you must check .ok.


3) Error Handling with XMLHttpRequest

const xhr = new XMLHttpRequest();
xhr.open("GET", "/api/data", true);

xhr.onload = function() {
  if (xhr.status >= 200 && xhr.status < 300) {
    console.log("Success:", xhr.responseText);
  } else {
    console.error("HTTP Error:", xhr.status, xhr.statusText);
  }
};

xhr.onerror = function() {
  console.error("Network error (server unreachable)");
};

xhr.ontimeout = function() {
  console.error("Request timed out");
};

xhr.timeout = 5000; // 5 seconds
xhr.send();

4) Handling Timeouts and Aborts

With fetch + AbortController

const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000); // 5 sec timeout

fetch("/api/slow", { signal: controller.signal })
  .then(res => res.json())
  .then(data => console.log("Data:", data))
  .catch(err => {
    if (err.name === "AbortError") {
      console.error("Request aborted (timeout).");
    } else {
      console.error("Error:", err.message);
    }
  })
  .finally(() => clearTimeout(timeout));

5) Best Practices for Error Handling

  1. Check status codes — don’t assume response is success.

  2. Handle network failures — always use .catch() or onerror.

  3. Use timeouts — don’t let requests hang forever.

  4. Fallbacks — show cached data, retry with backoff.

  5. User-friendly messages — don’t show “500 Internal Server Error” directly. Translate into something clear like “Server temporarily unavailable. Please try again later.”

  6. Retry strategy — exponential backoff (retry after 1s, 2s, 4s …).

  7. Centralized error handling — wrap your fetch/XHR in a utility function so you don’t duplicate error handling everywhere.


6) Example: Centralized Fetch Wrapper

async function safeFetch(url, options = {}) {
  try {
    const res = await fetch(url, options);

    if (!res.ok) {
      return { error: `HTTP ${res.status}: ${res.statusText}` };
    }

    const data = await res.json().catch(() => null); // safe JSON parsing
    return { data };
  } catch (err) {
    return { error: err.message || "Network error" };
  }
}

// Usage
safeFetch("/api/data").then(result => {
  if (result.error) {
    console.error("Error:", result.error);
  } else {
    console.log("Data:", result.data);
  }
});

Summary

  • Always check .ok / status.

  • Handle network errors with .catch() or onerror.

  • Add timeouts to avoid hanging requests.

  • Give friendly error messages and retry strategies.

  • Use a wrapper to keep code clean.