๐Ÿ” JavaScript

Production Fetch Patterns โ€” Timeout, Retry and AbortController

๐Ÿ“… Jul 4, 2026 โฑ 3 min read

Tutorial fetch assumes a happy network. Production networks time out, flake and race โ€” three patterns handle all of it.

1. Timeout with AbortController

async function fetchWithTimeout(url, ms = 8000) {
  const ctrl = new AbortController();
  const timer = setTimeout(() => ctrl.abort(), ms);
  try {
    const res = await fetch(url, { signal: ctrl.signal });
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    return await res.json();
  } finally {
    clearTimeout(timer);
  }
}

fetch has NO default timeout โ€” a hung request hangs forever without this.

2. Retry with exponential backoff

async function fetchRetry(url, tries = 3) {
  for (let i = 0; i < tries; i++) {
    try { return await fetchWithTimeout(url); }
    catch (err) {
      if (i === tries - 1) throw err;
      await new Promise(r => setTimeout(r, 500 * 2 ** i));  // 0.5s, 1s, 2s
    }
  }
}

Retry 5xx/network errors only โ€” retrying a 400 resends a bad request; retrying a POST can double-charge.

3. Cancel stale searches

let ctrl;
input.addEventListener("input", async () => {
  ctrl?.abort();                       // kill the previous request
  ctrl = new AbortController();
  try {
    const res = await fetch(`/api/search?q=${input.value}`, { signal: ctrl.signal });
    render(await res.json());
  } catch (e) {
    if (e.name !== "AbortError") throw e;   // aborts are expected
  }
});

Fast typing can return responses out of order โ€” cancellation guarantees only the latest renders. These three functions belong in every project's utils file.

โ† All Articles