Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/nodejs/undici/llms.txt

Use this file to discover all available pages before exploring further.

RetryHandler is a handler class (not a dispatcher) that wraps another handler and re-dispatches a request on failure. Unlike the interceptors.retry interceptor, which applies retry logic globally to every request on a dispatcher, RetryHandler gives you request-level control: you construct it explicitly for a single dispatch() call, making it ideal when different endpoints within the same application require different retry policies.

Constructor

new RetryHandler(options, retryHandlers)

options

options is an intersection of Dispatcher.DispatchOptions and RetryOptions. All standard dispatch options (path, method, headers, body, etc.) are passed through, and the retry configuration lives in the nested retryOptions key.
retryOptions
RetryOptions
default:"{}"
Retry-specific configuration. All fields are optional and fall back to their documented defaults.

retryHandlers

The second argument is an object with two required fields:
dispatch
function
required
The dispatch function to invoke on each retry attempt — typically client.dispatch.bind(client). This is called again with the same options (plus any Range headers if partial content was received) on each retry.
handler
Dispatcher.DispatchHandler
required
The downstream handler that receives events once the request ultimately succeeds or retries are exhausted. Must implement the standard dispatch handler interface (onRequestStart, onResponseStart, onResponseData, onResponseEnd, onResponseError).

Custom retry callback

When you provide a retry function in retryOptions, it is called after every failed attempt with three arguments:
retry(
  err: Error,                          // The error that caused the failure
  context: RetryContext,               // Current retry state and options
  callback: (err?: Error | null) => void  // Signal retry or abort
): void

RetryContext

state
RetryState
Current retry state. The counter property reflects the number of attempts made so far and can be read (but should not be mutated externally).
opts
Dispatcher.DispatchOptions & RetryOptions
The full merged options passed to the RetryHandler constructor. Useful for reading the current method, headers, or retry configuration inside the callback.
Call callback(null) to proceed with the next retry (after whatever delay your logic imposes), or callback(err) to stop retrying and propagate err to the handler’s onResponseError.

How RetryHandler differs from interceptors.retry

RetryHandlerinterceptors.retry
ScopeSingle dispatch() callEvery request on the dispatcher
ConfigurationPer-call in the constructorPer-dispatcher in .compose()
Use caseDifferent retry policies per endpointUniform retry policy across all requests
Access to response body on failureYes (with throwOnError: false)Yes
interceptors.retry is implemented by creating a RetryHandler internally on every request, merging global interceptor options with per-request retryOptions.

Partial body resume

RetryHandler is aware of partial responses. When a response with a known Content-Length or Content-Range is interrupted mid-stream, subsequent retry attempts add a Range: bytes=<offset>- header to resume from where the previous attempt left off. An If-Match header with the original ETag is included when available to detect resource changes between attempts.
Requests with stateful bodies — Readable streams or AsyncIterable instances that have already started emitting data — cannot be replayed. RetryHandler detects this and aborts with UND_ERR_REQ_RETRY rather than silently sending a truncated or incorrect body.

Examples

RetryHandler with default options
import { Client, RetryHandler } from 'undici'

const client = new Client('https://api.example.com')

const chunks = []
const handler = new RetryHandler(
  {
    origin: 'https://api.example.com',
    path: '/data',
    method: 'GET',
    retryOptions: {
      maxRetries: 5,
      minTimeout: 500,
      statusCodes: [429, 500, 502, 503, 504]
    }
  },
  {
    dispatch: client.dispatch.bind(client),
    handler: {
      onRequestStart (_controller, _context) {},
      onBodySent (_chunk) {},
      onResponseStart (_controller, statusCode, headers) {
        console.log('Response status:', statusCode)
      },
      onResponseData (_controller, chunk) {
        chunks.push(chunk)
      },
      onResponseEnd (_controller, _trailers) {
        console.log('Body:', Buffer.concat(chunks).toString())
      },
      onResponseError (_controller, err) {
        console.error('Final error after retries:', err)
      }
    }
  }
)

client.dispatch(
  { origin: 'https://api.example.com', path: '/data', method: 'GET' },
  handler
)

Build docs developers (and LLMs) love