beginner26 min

Fetch and Error Handling

Request data with the Fetch API and handle both success and failure paths.

What you'll learn

  • Explain what the fetch function does and what it returns
  • Handle a fetch response's JSON body with await
  • Use try/catch to handle a failed request without crashing the program

Prerequisites

Explanation

fetch is the browser's built-in function for making HTTP requests — the same request/response mechanics from the foundations track, but triggered by your own code instead of typing a URL. It returns a Promise that resolves to a Response object once the server replies:

const response = await fetch("/api/books");
const data = await response.json(); // parses the JSON body

Note the two awaits: the first waits for the response to arrive, the second waits for its body to be read and parsed as JSON (reading a body is itself asynchronous).

Because network requests can fail in many ways — no connection, a slow server, a bad URL, a server-side error — production code always needs a plan for failure. try/catch is how JavaScript handles that:

async function loadBooks() {
  try {
    const response = await fetch("/api/books");
    if (!response.ok) {
      throw new Error("Server responded with status " + response.status);
    }
    const data = await response.json();
    return data;
  } catch (error) {
    console.error("Failed to load books:", error.message);
    return [];
  }
}

Two details matter here. First, fetch only rejects (triggers catch) for network-level failures — a 404 or 500 response is still a "successful" fetch from JavaScript's point of view, which is why you must check response.ok (true for 2xx statuses) yourself and throw explicitly if it's false. Second, an unhandled rejected Promise (an async error nobody catches) is a real production bug — it can silently break a feature or spam error logs, so always wrap awaited calls that can fail in try/catch, and give the user a clear fallback state instead of a frozen or blank UI.

This sandbox has no real network access, so exercises here use a small mock fetch-like function that behaves the same way (returning a Promise, sometimes rejecting) so you can practice the exact same handling patterns you'll use with a real API.

Example

A mock fetch-like function handled with async/await and try/catch.

function mockFetchUser(id) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (id === 1) {
        resolve({ id: 1, name: "Ada" });
      } else {
        reject(new Error("User not found"));
      }
    }, 10);
  });
}

async function loadUser(id) {
  try {
    const user = await mockFetchUser(id);
    console.log("Loaded:", user.name);
  } catch (error) {
    console.error("Could not load user:", error.message);
  }
}

loadUser(1);
loadUser(99);

Try it yourself

Try calling loadUser with a different id and see the error path run.

Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.

Loading editor…

Guided exercise

Guided exercise

Using the provided mockFetchNumber(shouldFail) helper, write an async function `safeDouble(shouldFail)` that returns the doubled resolved number on success, or the string 'error' if the call rejects.

Checks: Success path doubles the resolved number · Failure path returns 'error' instead of rejecting

Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.

Loading editor…

Stuck? Get a hint.

Independent exercise

Independent exercise

Using the provided mockFetchBook(id) helper (rejects for id !== 1), write an async function `loadBookTitle(id)` that returns the book's title on success, or the string 'Book not found' on failure — without ever throwing an uncaught error.

Checks: Existing book resolves with its title · plus 1 hidden check

Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.

Loading editor…

Stuck? Get a hint.

Common mistakes

  • Not checking response.ok, so a 404 or 500 response is treated as if it succeeded.
  • Leaving an awaited call outside of any try/catch, letting a real network failure crash the whole feature.
  • Showing a blank or frozen UI on error instead of a clear, honest message to the user.

Knowledge check

Knowledge check

1. Does fetch's returned Promise reject for a 404 response?
2. What is the risk of an unhandled rejected Promise in production?
3. Where should you put code that might throw or reject, so a failure doesn't crash your program?

Takeaway

fetch gets you data over the network; try/catch and response.ok make sure failure has a real plan, not a crash.

Summary

fetch returns a Promise resolving to a Response; awaiting response.json() parses its body. Because fetch only rejects on network failure, you must check response.ok yourself, and try/catch keeps both network errors and bad statuses from crashing your program.

References

Your notes

Notes save automatically.

Finished this lesson?

Mark it complete to track your progress and schedule a future review.