Aborting Fetches

Cancel In-Flight Requests With `AbortController`

Aborting Fetches

Pass a `signal` to `fetch`; cancel it from the effect's cleanup. Saves bandwidth and avoids race conditions at the same time.

4 min read Level 3/5 #react#fetch#abort
What you'll learn
  • Use `AbortController` with `fetch`
  • Cancel from the effect cleanup
  • Handle the resulting `AbortError`

AbortController is a browser API that lets you cancel async work — including fetch. It pairs perfectly with effect cleanup: create a controller in the effect, abort it in the cleanup.

The Pattern

function UserCard({ id }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    const ctrl = new AbortController();

    fetch(`/api/users/${id}`, { signal: ctrl.signal })
      .then(r => r.json())
      .then(setUser)
      .catch(err => {
        if (err.name === "AbortError") return;   // ignore — we cancelled
        throw err;
      });

    return () => ctrl.abort();
  }, [id]);

  if (!user) return <p>Loading…</p>;
  return <p>{user.name}</p>;
}

When id changes, the cleanup calls ctrl.abort(). The browser cancels the in-flight request. The rejected promise produces an AbortError you ignore.

With async/await

useEffect(() => {
  const ctrl = new AbortController();

  (async () => {
    try {
      const res = await fetch(`/api/users/${id}`, { signal: ctrl.signal });
      setUser(await res.json());
    } catch (err) {
      if (err.name !== "AbortError") setError(err);
    }
  })();

  return () => ctrl.abort();
}, [id]);

Why Bother Aborting?

Even with a cancelled flag (previous lesson), the browser still downloads and parses the response. Aborting:

  • Saves bandwidth and CPU
  • Frees server resources sooner
  • Prevents the catch handler from firing with a stale response

For light, fast endpoints this is gravy. For slow or heavy endpoints it’s important.

Handling The Abort Error

AbortError is a normal rejection — fetch doesn’t have a special “cancelled” outcome. Code that wraps fetch with retries or error toasts needs to ignore AbortError explicitly.

.catch(err => {
  if (err.name === "AbortError") return;
  console.error(err);
  setError(err);
})

Beyond Fetch

Many APIs accept an AbortSignal:

  • fetch(url, { signal })
  • addEventListener(event, fn, { signal }) — listener auto-removes
  • setTimeout doesn’t, but you can roll your own via signal.addEventListener("abort", ...)

A single controller can manage multiple operations — cancel them all at once with one .abort().

Up Next

Beyond fetches — subscribing to event sources and websockets.

Subscriptions →