Skip to content
GreenSnippets

Respect Retry-After on 429 and 503 responses

Retry rate-limited requests after the delay the server asks for, instead of guessing.

JavaScript
function retryAfterMs(response, now = Date.now()) {
  const header = response.headers.get("retry-after");
  if (!header) return null;
  const seconds = Number(header);
  if (Number.isFinite(seconds)) return Math.max(0, seconds * 1000);
  const date = Date.parse(header);
  return Number.isNaN(date) ? null : Math.max(0, date - now);
}

async function fetchWithRetryAfter(url, init, { retries = 3, maxWaitMs = 30_000, sleep = (ms) => new Promise((r) => setTimeout(r, ms)) } = {}) {
  for (let attempt = 0; ; attempt++) {
    const response = await fetch(url, init);
    if (![429, 503].includes(response.status) || attempt >= retries) return response;
    const wait = retryAfterMs(response) ?? 500 * 2 ** attempt;
    await sleep(Math.min(wait, maxWaitMs));
  }
}

Example / expected output

Output
const res = await fetchWithRetryAfter("https://api.example.com/items");

How it works

When an API answers 429 Too Many Requests or 503, the Retry-After header says how long to wait, either in seconds or as an HTTP date. Honoring it is both faster and friendlier than blind backoff. Cap the wait and the number of retries so a bad server cannot stall you forever.