0PricingLogin
Node.js Backend Development Bootcamp · Lesson

Timeouts, Retries, and Exponential Backoff with Jitter

Bound every outbound call and retry transient failures without amplifying load on dependencies.

Why Bound Every Outbound Call

In a Node.js backend, every call that leaves your process (HTTP request, database query, message broker publish) is a place where you can hang forever. A slow dependency does not just delay one request, it pins an event-loop tick, holds a socket, and keeps a connection-pool slot busy.

  • Unbounded latency cascades: one stuck downstream call multiplies into thousands of stuck inbound requests.
  • Resource exhaustion: sockets, file descriptors, and pool connections leak while you wait.
  • No SLA without a deadline: you cannot promise a p99 latency if a call has no upper bound.

The first rule of production resilience: never make an outbound call without a timeout. Retries and backoff build on top of that bound.

Timeouts with AbortSignal.timeout

Modern Node.js (18+) ships AbortSignal.timeout(ms), which produces a signal that aborts automatically after the deadline. The global fetch accepts a signal, so bounding an HTTP call is a one-liner.

  • When the timeout fires, the promise rejects with an AbortError (err.name === 'AbortError').
  • The signal is fire-and-forget: no manual clearTimeout needed.
  • Always distinguish a timeout abort from other network errors so you log and retry correctly.
async function fetchWithTimeout(url, ms) {
  try {
    const res = await fetch(url, { signal: AbortSignal.timeout(ms) });
    if (!res.ok) throw new Error('HTTP ' + res.status);
    return await res.json();
  } catch (err) {
    if (err.name === 'AbortError' || err.name === 'TimeoutError') {
      throw new Error('Request timed out after ' + ms + 'ms');
    }
    throw err;
  }
}

// Demo against a hung promise (no real network)
function hang(signal) {
  return new Promise((_, reject) => {
    signal.addEventListener('abort', () => reject(signal.reason));
  });
}

(async () => {
  try {
    await hang(AbortSignal.timeout(50));
  } catch (e) {
    console.log('Aborted:', e.name);
  }
})();

All lessons in this course

  1. Timeouts, Retries, and Exponential Backoff with Jitter
  2. The Circuit Breaker Pattern and Bulkhead Isolation
  3. Graceful Shutdown and In-Flight Request Draining
  4. Health Checks, Readiness Probes, and Load Shedding
← Back to Node.js Backend Development Bootcamp