Handling Failure Like a Pro

Advanced · 24 min read · ▶ live playground · ✦ checkpoint

Real network code has more than success and crash. A request can return 404, take too long, be canceled by the user, produce an empty list, or fail after several attempts. Professional fetch code gives each of those states an explicit place in the UI.

The key upgrade from the last lesson is this: try/catch is necessary, but not enough. You must also inspect the response the server sent.

Check response.ok before reading success

fetch() rejects for network-level failures, such as being offline or aborting a request. It does not reject just because the server returned an HTTP error response.

That means a 404 Not Found still arrives as a fulfilled Response object:

const response = await fetch(url);
 
if (!response.ok) {
  throw new Error(`HTTP ${response.status}`);
}
 
const data = await response.json();

response.ok is true for successful 2xx status codes and false for status codes like 404 and 500. Check it before rendering success.

Try the three cases. They all reach the same host, but the UI treats them differently: success, non-ok error, and empty result.

web page — live previewnetwork
Run rebuilds the page

Notice the empty case is not an error. The server answered successfully, and the body is a real empty list. A good UI says "nothing to show" instead of pretending the app broke.

Cancel requests with AbortController

Some requests are no longer useful by the time they finish. The user navigated away, clicked a different filter, closed a panel, or pressed Cancel. AbortController is the standard cancellation tool for fetch.

The pattern is:

const controller = new AbortController();
const response = await fetch(url, { signal: controller.signal });
 
controller.abort(); // cancels the request if it is still in flight

A timeout is the same idea with a timer: start a controller, schedule controller.abort(), and clear the timer when the request finishes.

This demo requests JSONPlaceholder with a short delay. Click Cancel now while it is loading, or let the timeout abort it. If the service responds faster than expected, the success state explains that too.

web page — live previewnetwork
Run rebuilds the page

Cancellation is not a moral failure. It is part of the contract. If a request is stale, abort it and render the state that matches the user's current screen.

Retry only a bounded number of times

A retry is useful for failures that might be temporary: a timeout, a flaky connection, a brief server hiccup. A retry is not a magic eraser for a bad URL or missing resource. Retrying 404 usually just asks for the same missing thing again.

The safe retry pattern is bounded:

  1. Try the request.
  2. If the failure is retryable, wait a little.
  3. Try again.
  4. Stop after a fixed number of attempts and show an error if none worked.

The waiting gap is backoff. Backoff gives the network or server a little room before the next attempt. To make the control flow visible every time, this playground forces the first two attempts to behave like timeout failures, then performs a real JSONPlaceholder request on the final attempt.

web page — live previewnetwork
Run rebuilds the page

The important parts are the limits: a fixed attempts array, a fixed backoff array, and one final error state. Infinite retry loops punish the user and the server. Bounded retries make the promise clear: "I will try a few times, then I will tell you what happened."

UI states are part of the feature

Once a page talks to a server, the UI has at least four honest states:

  • Loading — the request has started and the result is not known yet.
  • Success — usable data arrived.
  • Empty — the request worked, but there is nothing to render.
  • Error — the page cannot produce the requested result.

Treat those states as normal paths, not apologies. A data screen that has a thoughtful empty state is easier to trust than one that shows a blank rectangle. An error state that names the problem gives the user and developer something to act on.

In the next lesson, the conversation changes shape again. Instead of one request followed by one response, you will compare polling, server-sent events, WebSockets, and streams: ways a page can keep receiving live data over time.

Checkpoint

Answer all three to mark this lesson complete

+50 XP on completion