p-retry

Retry a promise-returning or async function

p-retry downloads p-retry version p-retry license

p-retrySimilar Packages:

Npm Package Weekly Downloads Trend

3 Years
🌟 Show real-time usage chart on p-retry's README.md, just copy the code below.
## Usage Trend
[![Usage Trend of p-retry](https://npm-compare.com/img/npm-trend/THREE_YEARS/p-retry.png)](https://npm-compare.com/p-retry#timeRange=THREE_YEARS)

Cumulative GitHub Star Trend

🌟 Show GitHub stars trend chart on p-retry's README.md, just copy the code below.
## GitHub Stars Trend
[![GitHub Stars Trend of p-retry](https://npm-compare.com/img/github-trend/p-retry.png)](https://npm-compare.com/p-retry)

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
p-retry01,00225.5 kB14 days agoMIT

README for p-retry

p-retry

Retry a promise-returning or async function

It does exponential backoff and supports custom retry strategies for failed operations.

Install

npm install p-retry

Usage

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

const run = async () => {
	const response = await fetch('https://sindresorhus.com/unicorn');

	// Abort retrying if the resource doesn't exist
	if (response.status === 404) {
		throw new AbortError(response.statusText);
	}

	return response.blob();
};

console.log(await pRetry(run, {retries: 5}));

API

pRetry(input, options?)

Returns a Promise that is fulfilled when calling input returns a fulfilled promise. If calling input returns a rejected promise, input is called again until the max retries are reached, it then rejects with the last rejection reason.

Does not retry on most TypeErrors, with the exception of network errors. This is done on a best case basis as different browsers have different messages to indicate this. See whatwg/fetch#526 (comment)

Non-network TypeErrors always abort retries, even if shouldConsumeRetry or shouldRetry would otherwise allow another attempt.

input

Type: Function

Receives the number of attempts as the first argument and is expected to return a Promise or any value.

options

Type: object

onFailedAttempt(context)

Type: Function

Callback invoked on each failure. Receives a context object containing the error and retry state information.

The function is called after shouldConsumeRetry and before shouldRetry, for all errors except AbortError.

If the function throws, all retries will be aborted and the original promise will reject with the thrown error.

import pRetry from 'p-retry';

const run = async () => {
	const response = await fetch('https://sindresorhus.com/unicorn');

	if (!response.ok) {
		throw new Error(response.statusText);
	}

	return response.json();
};

const result = await pRetry(run, {
	onFailedAttempt: ({error, attemptNumber, retriesLeft, retriesConsumed, retryDelay}) => {
		console.log(`Attempt ${attemptNumber} failed. Retrying in ${retryDelay}ms. ${retriesLeft} retries left.`);
		// 1st request => Attempt 1 failed. Retrying in 1000ms. 5 retries left.
		// 2nd request => Attempt 2 failed. Retrying in 2000ms. 4 retries left.
		// …
	},
	retries: 5
});

console.log(result);

The context object contains:

  • error - The error that was thrown
  • attemptNumber - The attempt number (starts at 1)
  • retriesLeft - Number of retries remaining
  • retriesConsumed - Number of retries consumed so far
  • retryDelay - The delay in milliseconds before the next retry (based on minTimeout, factor, maxTimeout, and randomize). This is 0 when the retry is skipped or when no retry will occur based on the checks completed before the current callback runs.

The onFailedAttempt function can return a promise. For example, to add a delay:

import pRetry from 'p-retry';
import delay from 'delay';

const run = async () => { … };

const result = await pRetry(run, {
	onFailedAttempt: async () => {
		console.log('Waiting for 1 second before retrying');
		await delay(1000);
	}
});
shouldRetry(context)

Type: Function

Decide if a retry should occur based on context. Returning true triggers a retry, false aborts with the error.

The function is called after onFailedAttempt and shouldConsumeRetry.

The function is not called on AbortError, TypeError (except network errors), or if retries or maxRetryTime are exhausted.

If the function throws, all retries will be aborted and the original promise will reject with the thrown error.

import pRetry from 'p-retry';

const run = async () => { … };

const result = await pRetry(run, {
	shouldRetry: ({error, attemptNumber, retriesLeft}) => !(error instanceof CustomError)
});

In the example above, the operation will be retried unless the error is an instance of CustomError.

shouldConsumeRetry(context)

Type: Function

Decide if this failure should consume a retry from the retries budget.

When false is returned, the failure will not consume a retry or increment backoff values, but is still subject to maxRetryTime.

The function is called before onFailedAttempt and shouldRetry.

The function is not called on AbortError.

If the function throws, all retries will be aborted and the original promise will reject with the thrown error.

import pRetry from 'p-retry';

const run = async () => { … };

const result = await pRetry(run, {
	retries: 2,
	shouldConsumeRetry: ({error, retriesLeft}) => !(error instanceof RateLimitError),
});

In the example above, RateLimitErrors will not decrement the available retries.

retries

Type: number
Default: 10

The maximum amount of times to retry the operation.

factor

Type: number
Default: 2

The exponential factor to use.

minTimeout

Type: number
Default: 1000

The number of milliseconds before starting the first retry.

Set this to 0 to retry immediately with no delay.

maxTimeout

Type: number
Default: Infinity

The maximum number of milliseconds between two retries.

randomize

Type: boolean
Default: false

Randomizes the timeouts by multiplying with a factor between 1 and 2.

maxRetryTime

Type: number
Default: Infinity

The maximum time (in milliseconds) that the retried operation is allowed to run.

Measured with a monotonic clock (performance.now()) so system clock adjustments do not affect the limit.

signal

Type: AbortSignal

You can abort retrying using AbortController.

import pRetry from 'p-retry';

const run = async () => { … };
const controller = new AbortController();

cancelButton.addEventListener('click', () => {
	controller.abort(new Error('User clicked cancel button'));
});

try {
	await pRetry(run, {signal: controller.signal});
} catch (error) {
	console.log(error.message);
	//=> 'User clicked cancel button'
}
unref

Type: boolean
Default: false

Prevents retry timeouts from keeping the process alive.

Only affects platforms with a .unref() method on timeouts, such as Node.js.

makeRetriable(function, options?)

Wrap a function so that each call is automatically retried on failure.

import {makeRetriable} from 'p-retry';

const fetchWithRetry = makeRetriable(fetch, {retries: 5});

const response = await fetchWithRetry('https://sindresorhus.com/unicorn');

AbortError(message)

AbortError(error)

Abort retrying and reject the promise. No callback functions will be called.

message

Type: string

An error message.

error

Type: Error

A custom error.

Tip

You can pass arguments to the function being retried by wrapping it in an inline arrow function:

import pRetry from 'p-retry';

const run = async emoji => {
	// …
};

// Without arguments
await pRetry(run, {retries: 5});

// With arguments
await pRetry(() => run('🦄'), {retries: 5});

FAQ

How do I mock timers when testing with this package?

The package uses setTimeout and clearTimeout from the global scope, so you can use the Node.js test timer mocking or a package like sinon.

How do I stop retries when the process receives SIGINT (Ctrl+C)?

Use an AbortController to signal cancellation on SIGINT, and pass its signal to pRetry:

import pRetry from 'p-retry';

const controller = new AbortController();

process.once('SIGINT', () => {
	controller.abort(new Error('SIGINT received'));
});

try {
	await pRetry(run, {signal: controller.signal});
} catch (error) {
	console.log('Retry stopped due to:', error.message);
}

The package does not handle process signals itself to avoid global side effects.

Related