October DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsSlow PC?RecommendedPC slow today? Run a repair scan before it gets worseResolve common Windows issues and optimize system performance.Scan NowOctober DealsAmazon USDeal season is back - check today's better picksAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
World desk5 min

How to Add Timeouts and Retries to Fetch in TypeScript

Use fresh abort signals to time-limit each Fetch attempt, then retry only classified failures and operations that are safe to replay.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use an AbortSignal to put a time limit on each Fetch attempt, then add a finite retry loop only for failures your application can safely retry. Fetch does not reject just because a server returns an HTTP error such as 404: inspect response.ok or response.status separately from the exception path.

Set a timeout for one Fetch request

Fetch accepts an AbortSignal. When the signal is aborted, an in-flight request rejects with an abort error. A request may also be aborted after its headers arrive but before its body has been read, so code that consumes the response body must handle cancellation too. See MDN’s AbortController documentation.

As an Amazon Associate I earn from qualifying purchases.

Use AbortSignal.timeout() on supported runtimes

const response = await fetch("https://api.example.com/data", {
  signal: AbortSignal.timeout(5_000),
});

The five-second value is an example, not a universal recommendation. Choose a timeout that fits the endpoint and the caller’s latency budget. AbortSignal.timeout(ms) aborts with a TimeoutError DOMException. Its timer measures active time, so suspension or a document in the back-forward cache can affect when it expires. MDN marks the method Baseline 2024 and notes compatibility limits for older browsers. See MDN’s AbortSignal.timeout() reference.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Combine a timeout with caller cancellation

If a user navigates away or the calling operation is cancelled, preserve that signal as well as the timeout. Where AbortSignal.any() is available, combine the two:

const timeoutSignal = AbortSignal.timeout(5_000);
const signal = callerSignal
  ? AbortSignal.any([callerSignal, timeoutSignal])
  : timeoutSignal;

const response = await fetch(url, { signal });

The combined signal’s abort reason is available, but AbortSignal.any() does not expose a separate built-in flag identifying which input signal caused the abort. Check the reason or the original caller signal when deciding whether cancellation should be reported as a timeout or user cancellation. See MDN’s AbortSignal.any() reference.

Use a controller when you need timer cleanup

AbortSignal.timeout() does not let you cancel its timer. If you need to clear the timer when the request finishes early, use an AbortController and a timer for each attempt:

Rank #2
TypeScript Programming Language - Software Engineer & Coder T-Shirt
  • TypeScript implements a superset of syntax for strictly typed development, facilitating deep static analysis and enhanced development environment integration. The compiler translates source into standard script formats, ensuring parity across any runtime.
  • TypeScript is ideal for front-end developers, full-stack engineers, and software architects who build large-scale web applications. It serves those looking to improve code excellence, reduce bugs through static checking, and maintain complex projects more.
  • Lightweight, Classic fit, Double-needle sleeve and bottom hem
async function fetchWithTimeout(
  input: RequestInfo | URL,
  init: RequestInit = {},
  timeoutMs: number,
): Promise<Response> {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), timeoutMs);
  const callerSignal = init.signal;
  const onCallerAbort = () => controller.abort(callerSignal?.reason);

  if (callerSignal?.aborted) onCallerAbort();
  else callerSignal?.addEventListener("abort", onCallerAbort, { once: true });

  try {
    return await fetch(input, { ...init, signal: controller.signal });
  } finally {
    clearTimeout(timer);
    callerSignal?.removeEventListener("abort", onCallerAbort);
  }
}

This bounds the Fetch promise through receipt of the response headers. If the caller reads the body afterward, that work can still be interrupted by aborting the same signal; include body consumption inside the timed operation if the timeout is intended to cover it.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Keep HTTP errors separate from Fetch rejections

Fetch fulfills with a Response for HTTP statuses such as 404. It generally rejects for network or request failures instead. Therefore, decide what to do with a response outside the catch path:

const response = await fetch(url, { signal });

if (!response.ok) {
  // Apply this API's policy to response.status.
}

const data = await response.json();

See MDN’s Fetch guide for response handling and cancellation behavior.

Retry only when repeating the operation is safe

A timeout means the client stopped waiting; it does not prove the server never received or processed the request. Retrying a non-idempotent operation can duplicate a side effect if the first request succeeded but its response did not reach the client. Retry only operations that are safe to repeat, or use an API-supported idempotency mechanism and a way to reconcile uncertain outcomes.

There is no universal retry status list, retry count, timeout duration, or backoff formula established for every API. Set those from the service contract and operational needs. Before retrying, account for:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • Failure type: classify transient network failures separately from caller cancellation, timeouts, and other errors. Do not turn a caller’s cancellation into an automatic retry.
  • HTTP status: choose explicitly which response statuses, if any, the endpoint’s policy treats as retryable.
  • Attempt and time limits: cap both retries and total elapsed time to keep the caller’s latency budget bounded.
  • Delay: use an intentional backoff policy, with jitter where appropriate, rather than retrying immediately in a tight loop.
  • Server guidance: account for any applicable server retry limits or Retry-After instructions.
  • Request replayability: a consumed stream or one-shot request body may not be reusable. Build a fresh request or body for each attempt when needed.
  • Response-body handling: if retrying an HTTP response, consume or cancel its body before moving on, according to the API and runtime behavior.
  • Observability: record attempts and final outcomes so retries do not hide persistent failures.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Build a finite retry loop with a fresh signal per attempt

An aborted signal stays aborted. Create a new controller and timeout timer for every attempt; do not reuse the previous attempt’s signal. The following is a policy skeleton, not drop-in production code: define the status classifier, error classifier, delay, and backoff for the endpoint you are calling.

async function fetchWithRetry(
  input: RequestInfo | URL,
  init: RequestInit = {},
  options: { timeoutMs: number; maxRetries: number },
): Promise<Response> {
  let lastError: unknown;
  const callerSignal = init.signal;

  for (let attempt = 0; attempt <= options.maxRetries; attempt++) {
    const controller = new AbortController();
    const timer = setTimeout(() => controller.abort(), options.timeoutMs);
    const onCallerAbort = () => controller.abort(callerSignal?.reason);

    if (callerSignal?.aborted) onCallerAbort();
    else callerSignal?.addEventListener("abort", onCallerAbort, { once: true });

    try {
      const response = await fetch(input, {
        ...init,
        signal: controller.signal,
      });

      if (response.ok) return response;

      if (
        !shouldRetryStatus(response.status) ||
        attempt === options.maxRetries
      ) {
        return response;
      }

      await response.body?.cancel();
    } catch (error) {
      lastError = error;

      if (
        callerSignal?.aborted ||
        isTimeoutOrAbort(error) ||
        attempt === options.maxRetries ||
        !isTransientFailure(error)
      ) {
        throw error;
      }
    } finally {
      clearTimeout(timer);
      callerSignal?.removeEventListener("abort", onCallerAbort);
    }

    await delay(backoffWithJitter(attempt));
  }

  throw lastError;
}

The example deliberately returns a final HTTP error response rather than throwing it: its caller can inspect the status and body. You may instead choose to convert selected statuses into application errors, but keep that decision distinct from classifying Fetch rejections. Also ensure each retry reconstructs any non-replayable request body; passing the same consumed stream again is not a reliable retry strategy. For cancellation and exception details, see MDN’s AbortController documentation and MDN’s Fetch guide.

Check runtime and TypeScript support

Browser availability and TypeScript declarations are separate concerns: a method can exist at runtime while the project’s ambient library types do not include it, or declarations can be present for a runtime that lacks the method. MDN’s compatibility notes identify older-browser caveats for AbortSignal.timeout(). Node.js documentation also lists global Fetch and AbortSignal.timeout(), but the cited documentation is for Node.js v26.8.2. Confirm the actual deployed runtime and the project’s library/type declarations rather than assuming one tsconfig applies to browsers, Node.js, workers, and mixed projects. See Node.js v26.8.2 globals documentation.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More from the Wire

  1. World desk4 min
    How to Spot an AI Voice Scam Before Sending MoneyDon’t rely on how a caller sounds. Pause, call back through a known number, and verify the emergency with another trusted person before sending money.
  2. Mountain View desk4 min
    Google’s SynthID Detector: How to Check AI-Generated Images, Video and AudioGoogle’s SynthID Detector looks for an embedded watermark in supported images, video and audio. Here is what its results do—and do not—show.
  3. Redmond desk20 min
    How to create a link to File or Folder in Windows 11Windows 11 gives you several ways to point to a file or folder without moving or duplicating it. You can create a desktop shortcut,…
Recommended PC Tool
Recommended PC Tool
PC Slower Than It Used to Be?Free scan - under a minute
Crashes, No Sound, or Screen Glitches?Free driver scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.