Timeouts, retries and backoff

Most retry code makes outages worse. Learn which failures are worth a second attempt, how long to wait, and the one 429 you must never retry.

Networks fail in two ways: loudly and quietly. A loud failure is a refused connection or a 500, and your helper from the last two lessons already turns it into an exception. A quiet failure is a request that simply never finishes. Nothing throws, nothing logs, and the process sits there holding a connection until something upstream gives up. Retries have the opposite problem: written carelessly, they turn one failure into fifty and keep a rate limit going for minutes.

This lesson gives your helper a deadline, then teaches it to retry exactly the failures that a second attempt can fix.

Give every call a deadline

Neither fetch nor requests has a useful default timeout. Choose one per endpoint based on what it does. APIVerve's making requests guidance is 10 to 15 seconds for ordinary lookups and 30 seconds for rendering and extraction endpoints such as PDF or OCR, which do real work per call.

JavaScript: AbortSignal and AbortController

fetch accepts a signal. When the signal aborts, the request is cancelled and the Promise rejects. Node 18 and every current browser have a one-liner for a timer-driven signal:

const res = await fetch(url, {
  headers: { 'x-api-key': process.env.APIVERVE_API_KEY },
  signal: AbortSignal.timeout(10_000),
});

AbortSignal.timeout() is a shortcut for the longer form, which you still need when something other than a timer should cancel the request, such as a user navigating away or a newer search replacing an older one:

const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 10_000);
try {
  const res = await fetch(url, { headers, signal: controller.signal });
  // ...
} finally {
  clearTimeout(timer);
}

The finally matters. Without clearTimeout, the timer keeps your Node process alive for the full ten seconds after a fast response, and in a loop you accumulate timers.

A timed-out fetch rejects with an error whose name is TimeoutError (from AbortSignal.timeout) or AbortError (from controller.abort() with no reason). Check err.name, not the message text, which differs between runtimes.

Python: the timeout argument

requests takes timeout= on each call. A single number applies separately to connecting and to each wait between bytes of the response; a tuple sets them independently:

res = session.get(url, params=params, timeout=(3.05, 15))  # (connect, read)

A short connect timeout catches dead hosts quickly; a longer read timeout gives a slow endpoint room to answer. Note what the read timeout is not: it is not a cap on total duration. A server that trickles one byte every 14 seconds never trips it. If you need a hard wall-clock limit, enforce it around the call. Timeouts surface as requests.Timeout (with ConnectTimeout and ReadTimeout beneath it).

Check yourself

You call requests.get(url, timeout=15). Which statement is true?

Answer the check above to continue.

Run every example on your own key. Free plan, no card.

Scaling up?

Volume pricing, custom SLAs, and dedicated support for high-traffic teams.

Contact sales