Stockio

AbortController: Cancel Fetch Requests and Set Timeouts

Cancel browser fetch requests, implement reliable timeouts, and prevent stale responses with AbortController while keeping cleanup and user-facing errors predictable.

By Daniel Kim

React Developer ·

On this page

A search request can outlive the query that started it. A modal can close while its request is still downloading. A stalled connection can leave a loading indicator active indefinitely. AbortController gives browser code a standard cancellation signal, but reliable request handling also needs clear ownership, cleanup, and protection against stale completions.

Define who owns the request lifetime

Cancellation starts with a lifetime decision. A modal may own requests that should stop when it closes. A shared cache may intentionally keep requests running after one view disappears. Do not cancel shared work merely because one consumer loses interest.

Try the API latency tester with a safe public endpoint to inspect response timing before selecting a timeout policy. Individual measurements are only snapshots; browser scheduling, connectivity, and response size also influence how long users wait. Treat your deadline as a product decision, not a value copied from one successful test.

An AbortController owns a signal. Pass the signal to an API that supports it, then call abort when the operation is no longer useful. A signal communicates cancellation; it does not magically interrupt every arbitrary JavaScript function.

Pass a fresh signal into fetch

Each request lifetime needs an appropriate controller. Once a signal is aborted, it remains aborted. Reusing it for a new operation causes that operation to encounter cancellation immediately rather than resetting the old signal.

const controller = new AbortController();

async function load(signal: AbortSignal): Promise<unknown> {
const response = await fetch("/data/search.json", { signal });
if (!response.ok) throw new Error("HTTP " + response.status);
return response.json();
}

const pending = load(controller.signal);
pending.catch((error: unknown) => {
if (!controller.signal.aborted) console.error(error);
});

// Call when this operation is no longer needed:
controller.abort();

Aborting can affect the fetch and consumption of the response body. A request that has already produced a response object may still fail while JSON is being read. Keep response parsing inside the same error-handling boundary as the request.

The MDN AbortController reference describes the controller and its signal. Always handle the returned promise, including when cancellation is expected. Expected rejection still needs an observer to avoid unhandled rejections.

Implement a deadline with guaranteed cleanup

A portable timeout pattern uses a timer that aborts a dedicated controller. Clear the timer in finally so successful requests do not retain unnecessary callbacks. This is resource cleanup, separate from deciding which message users should see.

async function loadWithTimeout(url: string, timeoutMs: number) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeoutMs);
try {
  const response = await fetch(url, { signal: controller.signal });
  if (!response.ok) throw new Error("HTTP " + response.status);
  return await response.json();
} finally {
  clearTimeout(timer);
}
}

The await before response.json matters here: it keeps the timer active through body parsing and delays finally until that promise settles. Returning the parsing promise without await would clear the timer earlier.

Modern environments also offer AbortSignal.timeout and AbortSignal.any. The AbortSignal documentation describes their behavior and compatibility. Feature-check or provide a supported alternative for your target browsers. When combining user cancellation with a deadline, preserve enough context to explain which policy ended the operation.

Prevent older responses from winning

Aborting the previous request is helpful, but a latest-request guard is still valuable. Work might complete just before cancellation, a later transformation may not honor signals, or a mocked client may ignore them. Associate each request with an increasing generation number.

On a new search, increment the generation, capture it locally, and start the request. Before committing results, verify that the captured number still equals the current generation. Apply the same check when clearing loading state or showing an error, so an old finally block cannot reset a newer request's interface.

Do not equate cancellation with successful rollback. The remote server may already have processed a request before the browser aborts. This is particularly important for operations that change data. Browser cancellation means the client no longer wants to wait; it does not promise that a remote action never happened.

Keep request identity separate from the controller. Identity protects state ordering, while the signal asks cooperative work to stop. Together they provide stronger behavior than either mechanism used alone.

Test deadlines, cancellation, and cleanup separately

Use controlled promises or a local test endpoint to create slow responses. Test cancellation before a response arrives, during body reading, and after the operation has already settled. Confirm that repeated cancellation does not create additional visible failures.

A user who closes a modal generally does not need a red error message announcing cancellation. A deadline while a view remains open may need a retry affordance. Distinguish neutral cancellation from operational failure in the presentation layer without swallowing genuine failures inside unrelated operations.

The MDN fetch guide explains response status handling and cancellation in context. Remember that aborting cannot interrupt a long synchronous loop already blocking the main thread. Break CPU work into cooperative chunks or use an appropriate worker design when cancellation must remain responsive.

Keep request-lifetime examples with your browser JavaScript guides, and record ownership rules in your development references. Review timer counts, active listeners, and state updates after a view disappears. A cancellation implementation is complete only when the operation stops consuming unnecessary resources and no obsolete result can update the current interface.

Propagate cancellation through your own helpers

A helper that receives a signal should check whether it is already aborted before starting additional work. For a delay or another custom asynchronous operation, subscribing only to future abort events misses a signal that was cancelled earlier. Where supported, throwIfAborted provides a direct check; otherwise inspect the signal and reject consistently with your documented cancellation contract.

If you attach an abort listener manually, remove it when the operation settles normally as well as when cancellation occurs. A one-time listener removes itself after firing, but it does not automatically disappear after an unrelated successful completion. This listener lifecycle matters when a long-lived signal is shared across many short operations. Keep cleanup in one settlement path so repeated cancellation or racing completion cannot leave callbacks behind.

Distinguish an explicit user cancel from a deadline in your own operation metadata. The portable timeout example aborts without a custom reason, so its rejection alone does not tell the view whether a person cancelled or the timer fired. Record that policy deliberately rather than parsing an exception message. When using combined signals, inspect the chosen reason and account for your supported runtime behavior.

Finally, define what your timeout covers. The example keeps the deadline active while consuming the body, but a timer cannot preempt synchronous JavaScript that blocks the event loop. If expensive post-processing follows the download, add cooperative cancellation checks between chunks and retain the latest-request guard before committing state. A network deadline and a responsive computation budget are related concerns, not the same guarantee.

Key takeaways

  • Give each operation an explicit lifetime owner.
  • Use fresh signals for new request lifetimes.
  • Clear deadline timers in finally.
  • Pair cancellation with a latest-request identity guard.
  • Do not assume abort reverses remote side effects.
Featured sponsor

Reach designers & developers at work

Put your product next to the tools people use every day. One sponsor per placement, no tracking pixels.

Become a sponsor

Keep reading

Related articles

More in JavaScript & Frameworks
JavaScript & Frameworks

SSR vs SSG vs ISR vs SPA: Choosing a Rendering Strategy

Choose rendering strategies by freshness, personalization, and failure tolerance. Compare SSR, SSG, ISR, and SPA behavior with route-level examples and practical tests.

Includes: API latencySofia MarchettiSofia Marchetti
JavaScript & Frameworks

Debounce vs Throttle in JavaScript (With Code)

Choose debounce or throttle for search, resize, and pointer events. Implement cancellable helpers, understand timing tradeoffs, and prevent stale asynchronous results.

Includes: API latencyDaniel KimDaniel Kim
JavaScript & Frameworks

Loading Skeletons and Error States for Async API Data

Build resilient API interfaces with explicit loading states, stable skeletons, cancellation, and useful recovery actions that keep users in control.

Includes: API latencySofia MarchettiSofia Marchetti
JavaScript & Frameworks

Promise.all vs allSettled vs race vs any in JavaScript

Choose Promise.all, allSettled, race, or any based on failure behavior, then control concurrency and test asynchronous workflows without accidental serial execution.

Includes: API latencyDaniel KimDaniel Kim
DevOps & Deployment

10 Best Uptime Monitoring Tools

Compare ten uptime monitoring services for websites and APIs, from simple availability checks to incident workflows. Learn what each is suited to, what it cannot tell you, and how to choose.

Includes: API latencyRavi DesaiRavi Desai
Backend & APIs

12 Best Free Public APIs for Side Projects

Find approachable public APIs for weather, books, countries, and sample data, plus practical notes on access, limits, and choosing an endpoint for a side project.

Includes: API latencyChidi NwosuChidi Nwosu