undici-retry
undici-retry is a JavaScript/TypeScript library designed to integrate robust retry logic with the undici HTTP client. As of version 7.0.0, it provides mechanisms to automatically re-attempt failed HTTP requests based on configurable criteria such as status codes, timeouts, and custom delay resolvers. It is tightly coupled with `undici`, requiring `undici` version 7.0.0 or higher as a peer dependency, and is compatible with Node.js environments version 20 or newer. The library offers two primary functions: `sendWithRetry` for operations where the response body is consumed directly (e.g., JSON parsing), and `sendWithRetryReturnStream` for scenarios requiring stream-based body handling, which is crucial for large responses or custom stream processing. Its release cadence typically aligns with major `undici` releases, adapting to `undici`'s API changes and Node.js version support. Key differentiators include its tight integration with `undici`'s `Dispatcher` and `Client` types, and its `Either` pattern for error handling.
Common errors
-
TypeError: (0 , undici_retry_1.sendWithRetry) is not a function
cause Attempting to use `require()` to import `undici-retry` functions in a CommonJS context when the library is primarily designed for ESM.fixEnsure your project is configured for ESM (e.g., `"type": "module"` in `package.json`) and use `import { sendWithRetry } from 'undici-retry';`. If strictly bound to CommonJS, you might need a dynamic `import()` or transpilation. -
npm ERR! ERESOLVE unable to resolve dependency tree ... undici-retry@X.Y.Z requires a peer of undici@A.B.C but none is installed.
cause The installed version of `undici` does not satisfy the peer dependency requirements of `undici-retry`.fixInstall the correct version of `undici` that matches the peer dependency declared by your `undici-retry` version. For `undici-retry@7.x`, run `npm install undici@7`. -
Error: Request aborted by client. Reason: Aborted with cause: The connection was closed before the request could be completed.
cause Likely due to unconsumed or undumped response bodies from `sendWithRetryReturnStream`, leading to `undici` connection pool exhaustion.fixReview all calls to `sendWithRetryReturnStream` and ensure that `result.result.body` is either fully consumed (e.g., `await stream.text()`) or explicitly dumped (`await stream.dump()`) after use.
Warnings
- breaking Major versions of `undici-retry` are tightly coupled to specific major versions of its `undici` peer dependency. For instance, `undici-retry@4.x` requires `undici@6.x`, and `undici-retry@7.x` requires `undici@7.x`.
- breaking `undici-retry` versions 4.0.0 and above dropped support for Node.js 16. Newer versions require Node.js >=20.
- gotcha When using `sendWithRetryReturnStream`, the response body is returned as a Node.js `Readable` stream and is NOT automatically consumed. Failure to consume (e.g., by reading `stream.text()`, `stream.json()`, `stream.blob()`, or piping it) or explicitly dump (`stream.dump()`) the stream will lead to connection leaks and eventual `undici` resource exhaustion.
- gotcha By default, `throwOnInternalError` is `false` in `RequestParams` and `StreamedResponseRequestParams`. This means network-level errors (like `ECONNREFUSED` or `ENOTFOUND`) will be captured and returned within the `result.error` object, rather than throwing an exception.
Install
-
npm install undici-retry -
yarn add undici-retry -
pnpm add undici-retry
Imports
- sendWithRetry
const sendWithRetry = require('undici-retry').sendWithRetryimport { sendWithRetry } from 'undici-retry' - sendWithRetryReturnStream
const { sendWithRetryReturnStream } = require('undici-retry')import { sendWithRetryReturnStream } from 'undici-retry' - DEFAULT_RETRYABLE_STATUS_CODES
import undiciRetry from 'undici-retry'; undiciRetry.DEFAULT_RETRYABLE_STATUS_CODES
import { DEFAULT_RETRYABLE_STATUS_CODES } from 'undici-retry' - RetryConfig, RequestParams
import { RetryConfig, RequestParams } from 'undici-retry'import type { RetryConfig, RequestParams } from 'undici-retry'
Quickstart
import { sendWithRetry, DEFAULT_RETRYABLE_STATUS_CODES } from 'undici-retry';
import type { RetryConfig, RequestParams} from 'undici-retry'
import { Client } from 'undici';
import type { Dispatcher } from 'undici';
const client = new Client('http://localhost:3000', {}); // Replace with your target URL
async function makeRetriableRequest() {
const request: Dispatcher.RequestOptions = {
method: 'GET',
path: '/',
bodyTimeout: 500,
headersTimeout: 500,
};
const retryConfig: RetryConfig = {
maxAttempts: 3,
statusCodesToRetry: [429, 500, 502, 503, 504], // Customize retryable status codes
retryOnTimeout: true,
// delayResolver: (response, statusCodesToRetry) => { /* custom logic */ return 1000; }
};
const requestParams: RequestParams = {
safeParseJson: true,
blobBody: false,
throwOnInternalError: false,
};
console.log('Attempting request with retry logic...');
const result = await sendWithRetry(client, request, retryConfig, requestParams);
if (result.error) {
console.error('Request failed after all retries:');
console.error(JSON.stringify({
body: result.error.body, // The last error response body
headers: result.error.headers,
statusCode: result.error.statusCode,
message: result.error.error?.message, // Error message if an internal error occurred
}, null, 2));
} else if (result.result) {
console.log('Request successful:');
console.log(JSON.stringify({
body: result.result.body,
headers: result.result.headers,
statusCode: result.result.statusCode,
}, null, 2));
}
await client.close(); // Important: Close the client when no longer needed
}
makeRetriableRequest();