mrkeyoor.com_
Sun 20 Sept 07:02 UTC
npmUtilsupdated 20 Sept 2026

p-retry review

p-retry repeatedly calls a promise-returning function until it succeeds, a retry policy rejects the failure, an abort signal fires, or its attempt or time budget ends. It supplies exponential delay, jitter, retry classification, failures that do not consume the attempt budget, and makeRetriable() for wrapping an existing function. Version 8 requires Node 22, exposes the calculated retryDelay to failure callbacks, changes callback order so budget consumption is decided first, and tightens handling of TypeError, timing options, and callback validation.

37.8Mdownloads / wk
Verdict

p-retry is a good small policy layer for idempotent async work when Node 22 is acceptable and your code can classify failures. Skip it when retries require shared circuit state, exact server delay handling, or protection against duplicated side effects.

We installed it

Lab card: what happened when we installed p-retryScreenshot of p-retry documentation
Install✓ · 0.6s2 packages on disk · 1 MB
ImportESM import works · require() works · ESM package with exports map
Browser1.8 KBgzipped (4.4 KB minified), bundled with esbuild
TypesTypeScript types bundled
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does p-retry install cleanly?

Yes. In a fresh container with an empty cache, npm install p-retry finished in 0.6s, leaving 2 packages and 1 MB on disk. npm audit reported no known vulnerabilities.

How much does p-retry add to a browser bundle?

1.8 KB gzipped (4.4 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.

Does p-retry work with both ESM and CommonJS?

Yes. Both import 'p-retry' and require('p-retry') worked in Node 22 in our run. The package is published as ESM with an exports map.

Does p-retry include TypeScript types?

Yes, type declarations ship inside the package, so no @types install is needed.

p-retry or async-retry: which should you use?

async-retry: Use it for a compact bail-and-retry API on older Node versions. p-retry is a good small policy layer for idempotent async work when Node 22 is acceptable and your code can classify failures.

When should you not use p-retry?

The operation is not idempotent and has no idempotency key; replaying a payment, message, or mutation can duplicate real work

API stability3/5The default pRetry(input, options) shape remains simple, but recent majors changed meaningful contracts. Version 7 rewrote the implementation, moved callback state into context objects, removed forever, and added makeRetriable. Version 8 raises the runtime floor and reorders shouldConsumeRetry, onFailedAttempt, and shouldRetry. Basic calls migrate easily, while custom policies must be tested because ordering decides which counters and delays a callback observes.
Docs5/5The README defines every option with types and defaults, documents callback order and excluded error classes, and includes examples for AbortSignal, failure logging, budget consumption, makeRetriable, timer mocks, and SIGINT. It distinguishes retry count from maxRetryTime and explains unref. The missing application-level piece is intentional: users still have to decide which HTTP statuses and business operations are safe to replay.
Maintenance4/5The repository is unarchived, GitHub lists one open issue and pull request, and version 8.0.0 was released on March 26, 2026 with the latest push on the same date. That release fixes TypeError retry handling and tightens timing and callback validation in addition to its breaking changes. Development is focused and low-volume, which suits a small scheduler, though consumers should expect runtime floors to rise with majors.
Ecosystem5/5npm recorded 49,060,698 downloads for August 16 through August 22, 2026, and GitHub reports 1,028 stars. The package has one direct dependency, bundled TypeScript declarations, a 1.8 KB measured gzip browser build, and working import and require() paths in our Node sandbox. Its API composes with fetch, database clients, queues, and any promise-returning function without asking those libraries to adopt a wrapper type.

Use it if

  • A network or distributed-system operation fails transiently and the caller owns a clear retry policy
  • You need exponential delay, jitter, an elapsed-time ceiling, and AbortSignal support without writing a scheduling loop
  • Rate-limit failures should wait and retry without consuming the same budget used for ordinary transient errors
  • Several calls to one async function should share a tested policy through makeRetriable()
Skip it if

Setup reality

Our fresh Node 22 install of p-retry 8.0.0 completed in 0.6 seconds. Two packages occupied 1 MB afterward, and npm audit reported zero known vulnerabilities. p-retry declares one direct dependency, no peers, and a Node 22 minimum. Its own unpacked package measured 40 KB. TypeScript declarations are bundled. It declares ESM with an exports map, while both import and require() worked in our sandbox. A complete browser import measured 4.4 KB minified and 1.8 KB gzipped.

There are no credentials or config files. The input function receives an attempt number and must throw or reject on failure. fetch does not reject for HTTP error statuses, so check response.ok and throw an error that shouldRetry can classify. AbortError skips all callbacks and ends immediately. Other TypeError failures normally stop as programming errors; the package makes a best-effort exception for recognized network TypeErrors.

The default is ten retries after the initial call, with exponential delay starting at one second. Set retries, minTimeout, maxTimeout, factor, randomize, and maxRetryTime from the operation's real deadline. randomize adds jitter but does not read Retry-After. Version 8 calls shouldConsumeRetry before onFailedAttempt and shouldRetry, and the callback context now includes retryDelay. Code written around the old order must be reviewed during upgrade.

Cancellation stops the retry schedule through AbortSignal, but the running operation also needs the signal if it should stop mid-attempt. The library does not install SIGINT handlers. In short-lived Node commands, unref prevents a pending delay timer from keeping the process alive. A failure excluded by shouldConsumeRetry still counts against maxRetryTime, which is the safety boundary for rate-limit loops.

Patterns

Retry a failing async function retry-async-operation

import pRetry from 'p-retry';

const value = await pRetry(
  attempt => readRemoteValue({ attempt }),
  { retries: 4 },
);

retries counts repeats after the first attempt, so this configuration permits five calls.

Throw on retryable HTTP responses classify-http-status

import pRetry, { AbortError } from 'p-retry';

const data = await pRetry(async () => {
  const response = await fetch(url);
  if (response.status === 429 || response.status >= 500) {
    throw new Error(`temporary HTTP ${response.status}`);
  }
  if (!response.ok) throw new AbortError(`HTTP ${response.status}`);
  return response.json();
});

fetch resolves for HTTP errors. AbortError ends without running retry callbacks.

Retry only selected failures filter-retry-errors

await pRetry(run, {
  shouldRetry: ({ error }) =>
    error instanceof NetworkError || error instanceof TimeoutError,
});

shouldRetry runs after budget consumption and onFailedAttempt in version 8.

Keep rate limits outside the retry count exclude-rate-limit-budget

await pRetry(run, {
  retries: 3,
  maxRetryTime: 30_000,
  shouldConsumeRetry: ({ error }) => !(error instanceof RateLimitError),
});

Excluded failures still consume elapsed time, so keep maxRetryTime finite.

Report the next scheduled delay log-retry-delay

await pRetry(run, {
  onFailedAttempt: ({ error, attemptNumber, retriesLeft, retryDelay }) => {
    console.warn({ error, attemptNumber, retriesLeft, retryDelay });
  },
});

retryDelay is new in version 8 and is zero when no retry will be scheduled.

Bound exponential backoff set-backoff-policy

await pRetry(run, {
  retries: 6,
  factor: 2,
  minTimeout: 250,
  maxTimeout: 5_000,
  randomize: true,
  maxRetryTime: 20_000,
});

maxRetryTime uses a monotonic clock and caps the whole retry operation.

Cancel waiting and active work cancel-retry-loop

const controller = new AbortController();
process.once('SIGINT', () => controller.abort(new Error('SIGINT')));

await pRetry(
  () => fetch(url, { signal: controller.signal }),
  { signal: controller.signal },
);

Pass the signal to pRetry and the operation; the package does not register process handlers itself.

Apply one policy to every call wrap-retriable-function

import { makeRetriable } from 'p-retry';

const fetchWithRetry = makeRetriable(fetch, {
  retries: 3,
  maxRetryTime: 10_000,
});
const response = await fetchWithRetry(url, options);

Arguments and return types follow the wrapped function, but HTTP status classification still belongs in that function.

Alternatives

PackageRegistryPick it when
async-retrynpmUse it for a compact bail-and-retry API on older Node versions
promise-retrynpmUse it when existing code already follows the retry module's operation model
p-timeoutnpmUse it when the requirement is one deadline without replaying the operation

More utils guides

lru-cache · ajv · type-fest · p-limit · find-up · js-yaml · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.