Cancel Async Requests in TypeScript

Learn to cancel in-flight async requests using AbortController with TypeScript types. Covers AbortSignal, fetch cancellation, cleanup patterns, and typed error handling.

7 min read

Canceling an in-flight request means telling the browser to stop waiting for a response. In TypeScript, you do this with the AbortController API, which is fully typed in the DOM library. The pattern is straightforward: create a controller, pass its signal to fetch, and call abort when you no longer need the result.

This matters for search-as-you-type inputs, page navigations where a pending request is now stale, and component unmounts in React or similar frameworks.

The Basic AbortController Pattern

Create an AbortController instance and pass its signal property to the fetch call. The signal is typed as AbortSignal, and TypeScript knows it can be passed directly to fetch's options.

typescripttypescript
const controller = new AbortController();
 
fetch("/api/data", { signal: controller.signal })
  .then((response) => response.json())
  .catch((error) => {
    if (error instanceof DOMException && error.name === "AbortError") {
      console.log("Request was cancelled");
    }
  });
 
controller.abort();

When abort is called, the fetch promise rejects with an AbortError. This is a DOMException, not a regular Error, so the check uses error.name instead of instanceof. The type system distinguishes AbortError from network failures, letting you handle cancellation separately.

Detecting Cancellation vs Failure

A cancelled request is not a failure. It means you deliberately stopped waiting. Separate this case from real errors in your catch block.

typescripttypescript
async function fetchWithCancel(url: string, signal: AbortSignal) {
  try {
    const response = await fetch(url, { signal });
    return await response.json();
  } catch (error) {
    if (error instanceof DOMException && error.name === "AbortError") {
      return null;
    }
    throw error;
  }
}

The function returns null for cancellations and re-throws actual errors. Callers can check for null to know the request was aborted. The type signature signal: AbortSignal documents that the function supports cancellation at the type level.

Cancelling on Component Unmount

In React and similar frameworks, a component might start a fetch and unmount before the response arrives. Abort the request in the cleanup function.

typescripttypescript
useEffect(() => {
  const controller = new AbortController();
 
  async function loadData() {
    const data = await fetchWithCancel("/api/data", controller.signal);
    if (data !== null) {
      setData(data);
    }
  }
 
  loadData();
 
  return () => controller.abort();
}, []);

The cleanup function runs when the component unmounts or the dependencies change. Calling abort ensures the in-flight request does not try to update state on an unmounted component. The null check after fetchWithCancel prevents the state update when the request was cancelled.

AbortController lifecycle in a component

The diagram shows the two paths. Either the response arrives and state updates, or the component unmounts, the controller aborts, and the catch block safely ignores the cancelled request.

Building a Timeout Wrapper

AbortController also works for timeouts. Create a wrapper that cancels the request if it takes too long.

typescripttypescript
async function fetchWithTimeout(url: string, timeoutMs: number): Promise<Response> {
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
 
  try {
    const response = await fetch(url, { signal: controller.signal });
    return response;
  } finally {
    clearTimeout(timeoutId);
  }
}

The timeout calls abort after the specified duration. The finally block cleans up the timeout regardless of whether the request succeeded or failed. If the request completes before the timeout, the clearTimeout prevents an unnecessary abort call.

Callers can distinguish a timeout from other failures by checking for AbortError in their catch block. The wrapper does not throw a custom error; it lets the natural AbortError propagate so callers can handle it uniformly.

Passing Signals Through Your API Layer

When you have a typed fetch wrapper, thread the signal parameter through every function that makes requests.

typescripttypescript
async function apiGet<T>(
  url: string,
  signal?: AbortSignal
): Promise<T> {
  const response = await fetch(url, { signal });
 
  if (!response.ok) {
    throw new Error(`Request failed: ${response.status}`);
  }
 
  return response.json() as T;
}

The optional signal parameter lets callers opt into cancellation without changing the function signature for code that does not need it. When signal is provided, fetch uses it. When it is undefined, fetch behaves normally. This pattern makes cancellation available everywhere without forcing every caller to create a controller.

Common Mistakes

Not checking for AbortError. A cancelled request throws an AbortError, not a regular Error. If your catch block assumes all errors are Error instances, the AbortError will pass through unhandled. Always check error.name before assuming the error type.

Creating one AbortController for many requests. A single controller can only be aborted once. If you pass the same signal to multiple fetch calls, aborting it cancels all of them. This may be what you want, but if you need independent cancellation, create separate controllers.

Forgetting to call abort on unmount. If a component unmounts and an in-flight request later resolves and calls setState, React logs a warning. The abort pattern in the cleanup function prevents this entirely.

For handling errors after cancellation, see handle API errors in TypeScript. For retrying after a timeout, see retry failed API requests with TypeScript. For typing the fetch calls being cancelled, see build a typed fetch wrapper.

Rune AI

Rune AI

Key Insights

  • Use AbortController to cancel in-flight fetch requests by passing its signal.
  • The abort method triggers cancellation. The signal's aborted property checks state.
  • A cancelled fetch rejects with an AbortError. Check error.name to detect it.
  • Create AbortController in useEffect and abort in the cleanup function.
  • Timeout wrappers can use AbortController to cancel slow requests automatically.
RunePowered by Rune AI

Frequently Asked Questions

What is AbortController in TypeScript?

AbortController is a browser API that lets you cancel in-flight web requests. It has a signal property of type AbortSignal that you pass to fetch, and an abort method that triggers cancellation. TypeScript includes types for both in its DOM library.

How do I detect if a request was cancelled vs failed?

Check if error.name is 'AbortError'. When a request is aborted, fetch rejects with an AbortError DOMException. You can distinguish this from network errors and HTTP errors by checking the error name.

Should I abort requests when a React component unmounts?

Yes. If a component starts a fetch and unmounts before the response arrives, calling setState on an unmounted component causes a warning. Abort the request in the useEffect cleanup function to prevent this.

Conclusion

Canceling async requests in TypeScript uses the AbortController API, which is fully typed in TypeScript's DOM library. Pass the signal to fetch, call abort when you need to cancel, and check for AbortError in your catch block. The type system helps you distinguish cancellation from real failures, so your error handling stays precise even when requests are interrupted.