Who this is for: code that calls pRetry() and now also needs a timeout per attempt, a circuit breaker, typed errors, or a fake clock in tests.
For a one-off retry, retry() from @ylcnfrht/resilience is the direct replacement: a function and an options object, no pipeline to build. It only bundles the retry strategy (4.1 KB min+gzip against p-retry’s 1.8 KB). When you need more than retries, build a pipeline and pass the same options to addRetry().
if (response.status ===404) thrownewAbortError(response.statusText);
if (!response.ok) thrownewError(response.statusText);
return response.blob();
},
{ retries: 5 },
);
import {
classNonRetryableError
Throw (or wrap an error in) this to stop retrying right away: retry gives up and the caller receives the original error.
Like p-retry's AbortError. throw new NonRetryableError(error) or throw new NonRetryableError('invalid card').
Throw (or wrap an error in) this to stop retrying right away: retry gives up and the caller receives the original error.
Like p-retry's AbortError. throw new NonRetryableError(error) or throw new NonRetryableError('invalid card').
NonRetryableError(
constresponse:Response
response.
Response.statusText: string
The statusText read-only property of the Response interface contains the status message corresponding to the HTTP status code in Response.status.
The ok read-only property of the Response interface contains a Boolean stating whether the response was successful (status in the range 200-299) or not.
backoffType: 'exponential', delay: 1000 gives p-retry’s default delays (1 s, 2 s, 4 s…). Like AbortError, NonRetryableError stops at once and the caller receives the wrapped error, not the wrapper.
After every handled failure, the last one included, with how many retries are left.
onFailedAttempt: ({
outcome: Outcome<string, unknown>
outcome,
attemptNumber: number
0 for the first (original) attempt.
attemptNumber,
retriesLeft: number
Retries still allowed after this failure (0 on the last one).
retriesLeft }) => {
if (!
outcome: Outcome<string, unknown>
outcome.
ok: boolean
ok)
constlog: {
warn(msg:string):void;
}
log.
functionwarn(msg:string):void
warn(`Attempt ${
attemptNumber: number
0 for the first (original) attempt.
attemptNumber+1} failed: ${
var String:StringConstructor
(value?:any) => string
Allows manipulation and formatting of text strings and determination and location of substrings within strings.
String(
outcome: {
readonly ok: false;
readonly error: unknown;
}
outcome.
error: unknown
error)}. ${
retriesLeft: number
Retries still allowed after this failure (0 on the last one).
retriesLeft} left.`);
},
});
shouldHandle also sees successful outcomes, so check outcome.ok first. Returning true for a success retries it, which is how you retry on a result (for example a Response with status 503).
Polly-style builder with a shortcut for every strategy: addRetry(), addTimeout(), addCircuitBreaker(), …
Convenient, but it bundles every strategy. For the smallest browser bundle use PipelineBuilder with
retryStrategy(), timeoutStrategy(), … instead; the two are interchangeable.
The type parameters track which errors the pipeline can raise, for pipeline.settle() + Outcome.match().
Polly-style builder with a shortcut for every strategy: addRetry(), addTimeout(), addCircuitBreaker(), …
Convenient, but it bundles every strategy. For the smallest browser bundle use PipelineBuilder with
retryStrategy(), timeoutStrategy(), … instead; the two are interchangeable.
The type parameters track which errors the pipeline can raise, for pipeline.settle() + Outcome.match().
For HTTP, createResilientFetch() is usually the better replacement: it retries 5xx and 429 responses (fetch resolves them, so neither p-retry nor wrap(fetch) sees them as failures), honours Retry-After, and only retries idempotent methods.
Polly-style builder with a shortcut for every strategy: addRetry(), addTimeout(), addCircuitBreaker(), …
Convenient, but it bundles every strategy. For the smallest browser bundle use PipelineBuilder with
retryStrategy(), timeoutStrategy(), … instead; the two are interchangeable.
The type parameters track which errors the pipeline can raise, for pipeline.settle() + Outcome.match().
Polly-style builder with a shortcut for every strategy: addRetry(), addTimeout(), addCircuitBreaker(), …
Convenient, but it bundles every strategy. For the smallest browser bundle use PipelineBuilder with
retryStrategy(), timeoutStrategy(), … instead; the two are interchangeable.
The type parameters track which errors the pipeline can raise, for pipeline.settle() + Outcome.match().
Defaults. p-retry: 10 retries, exponential from 1 s. Ours (Polly’s): 3 retries, a constant 2 s. Set maxRetryAttempts, backoffType and delay explicitly.
Jitter. p-retry’s randomize multiplies each delay by 1 to 2, so delays only get longer. Our useJitter: true is decorrelated jitter for exponential backoff, 'full' picks between 0 and the delay, 'equal' between half and the full delay. None matches randomize exactly; 'full' spreads a fleet of clients best.
Attempt numbers are 0-based. p-retry’s first attempt is attemptNumber 1; our context.attemptNumber and args.attemptNumber are 0 on the first call.
Callback order. p-retry calls shouldConsumeRetry, then onFailedAttempt, then shouldRetry, and onFailedAttempt runs for every failure. We call shouldHandle first; shouldConsumeRetry and onFailedAttempt only run for failures it accepts.
Uncounted retries still wait. When p-retry’s shouldConsumeRetry returns false, it retries at once. Ours retries after the normal delay.
maxRetryTime. p-retry shortens the last delay to fit the remaining time. We stop when the next delay would end past maxRetryTime. Neither interrupts an attempt in flight; add a timeout for that.
Errors in callbacks. Same in both: a callback that throws ends the call with that error, without further retries.
Abort. Both stop retrying when the signal aborts, and neither calls your function if it is already aborted. p-retry always rejects with the abort reason. Ours rejects with the reason when the abort lands during a delay, and with the attempt’s own error when it lands during an attempt. Ours also passes the signal to your function through context.signal, so the attempt itself can be cancelled.
Non-idempotent calls.execute(fn, { idempotent: false }) only repeats calls that never started (a rejection by another strategy). p-retry has no equivalent.