first commit

This commit is contained in:
Stefan Hacker
2026-04-03 09:38:48 +02:00
commit 37ad745546
47450 changed files with 3120798 additions and 0 deletions
+134
View File
@@ -0,0 +1,134 @@
import {type OperationOptions} from 'retry';
export class AbortError extends Error {
readonly name: 'AbortError';
readonly originalError: Error;
/**
Abort retrying and reject the promise.
@param message - An error message or a custom error.
*/
constructor(message: string | Error);
}
export type FailedAttemptError = {
readonly attemptNumber: number;
readonly retriesLeft: number;
} & Error;
export type Options = {
/**
Callback invoked on each retry. Receives the error thrown by `input` as the first argument with properties `attemptNumber` and `retriesLeft` which indicate the current attempt number and the number of attempts left, respectively.
The `onFailedAttempt` function can return a promise. For example, to add a [delay](https://github.com/sindresorhus/delay):
```
import pRetry from 'p-retry';
import delay from 'delay';
const run = async () => { ... };
const result = await pRetry(run, {
onFailedAttempt: async error => {
console.log('Waiting for 1 second before retrying');
await delay(1000);
}
});
```
If the `onFailedAttempt` function throws, all retries will be aborted and the original promise will reject with the thrown error.
*/
readonly onFailedAttempt?: (error: FailedAttemptError) => void | Promise<void>;
/**
Decide if a retry should occur based on the error. Returning true triggers a retry, false aborts with the error.
It is not called for `TypeError` (except network errors) and `AbortError`.
@param error - The error thrown by the input function.
@example
```
import pRetry from 'p-retry';
const run = async () => { … };
const result = await pRetry(run, {
shouldRetry: error => !(error instanceof CustomError);
});
```
In the example above, the operation will be retried unless the error is an instance of `CustomError`.
*/
readonly shouldRetry?: (error: FailedAttemptError) => boolean | Promise<boolean>;
/**
You can abort retrying using [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/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'
}
```
*/
readonly signal?: AbortSignal;
} & OperationOptions;
/**
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](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#Checking_that_the_fetch_was_successful) to indicate this.
See [whatwg/fetch#526 (comment)](https://github.com/whatwg/fetch/issues/526#issuecomment-554604080)
@param input - Receives the number of attempts as the first argument and is expected to return a `Promise` or any value.
@param options - Options are passed to the [`retry`](https://github.com/tim-kos/node-retry#retryoperationoptions) module.
@example
```
import pRetry, {AbortError} from 'p-retry';
import fetch from 'node-fetch';
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}));
// With the `onFailedAttempt` option:
const result = await pRetry(run, {
onFailedAttempt: error => {
console.log(`Attempt ${error.attemptNumber} failed. There are ${error.retriesLeft} retries left.`);
// 1st request => Attempt 1 failed. There are 4 retries left.
// 2nd request => Attempt 2 failed. There are 3 retries left.
// …
},
retries: 5
});
console.log(result);
```
*/
export default function pRetry<T>(
input: (attemptCount: number) => PromiseLike<T> | T,
options?: Options
): Promise<T>;
+92
View File
@@ -0,0 +1,92 @@
import retry from 'retry';
import isNetworkError from 'is-network-error';
export class AbortError extends Error {
constructor(message) {
super();
if (message instanceof Error) {
this.originalError = message;
({message} = message);
} else {
this.originalError = new Error(message);
this.originalError.stack = this.stack;
}
this.name = 'AbortError';
this.message = message;
}
}
const decorateErrorWithCounts = (error, attemptNumber, options) => {
// Minus 1 from attemptNumber because the first attempt does not count as a retry
const retriesLeft = options.retries - (attemptNumber - 1);
error.attemptNumber = attemptNumber;
error.retriesLeft = retriesLeft;
return error;
};
export default async function pRetry(input, options) {
return new Promise((resolve, reject) => {
options = {...options};
options.onFailedAttempt ??= () => {};
options.shouldRetry ??= () => true;
options.retries ??= 10;
const operation = retry.operation(options);
const abortHandler = () => {
operation.stop();
reject(options.signal?.reason);
};
if (options.signal && !options.signal.aborted) {
options.signal.addEventListener('abort', abortHandler, {once: true});
}
const cleanUp = () => {
options.signal?.removeEventListener('abort', abortHandler);
operation.stop();
};
operation.attempt(async attemptNumber => {
try {
const result = await input(attemptNumber);
cleanUp();
resolve(result);
} catch (error) {
try {
if (!(error instanceof Error)) {
throw new TypeError(`Non-error was thrown: "${error}". You should only throw errors.`);
}
if (error instanceof AbortError) {
throw error.originalError;
}
if (error instanceof TypeError && !isNetworkError(error)) {
throw error;
}
decorateErrorWithCounts(error, attemptNumber, options);
if (!(await options.shouldRetry(error))) {
operation.stop();
reject(error);
}
await options.onFailedAttempt(error);
if (!operation.retry(error)) {
throw operation.mainError();
}
} catch (finalError) {
decorateErrorWithCounts(finalError, attemptNumber, options);
cleanUp();
reject(finalError);
}
}
});
});
}
+9
View File
@@ -0,0 +1,9 @@
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+57
View File
@@ -0,0 +1,57 @@
{
"name": "p-retry",
"version": "6.2.1",
"description": "Retry a promise-returning or async function",
"license": "MIT",
"repository": "sindresorhus/p-retry",
"funding": "https://github.com/sponsors/sindresorhus",
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "https://sindresorhus.com"
},
"type": "module",
"exports": "./index.js",
"types": "./index.d.ts",
"sideEffects": false,
"engines": {
"node": ">=16.17"
},
"scripts": {
"test": "xo && ava && tsd"
},
"files": [
"index.js",
"index.d.ts"
],
"keywords": [
"promise",
"retry",
"retries",
"operation",
"failed",
"rejected",
"try",
"exponential",
"backoff",
"attempt",
"async",
"await",
"promises",
"concurrently",
"concurrency",
"parallel",
"bluebird"
],
"dependencies": {
"@types/retry": "0.12.2",
"is-network-error": "^1.0.0",
"retry": "^0.13.1"
},
"devDependencies": {
"ava": "^5.3.1",
"delay": "^6.0.0",
"tsd": "^0.28.1",
"xo": "^0.56.0"
}
}
+184
View File
@@ -0,0 +1,184 @@
# p-retry
> Retry a promise-returning or async function
It does exponential backoff and supports custom retry strategies for failed operations.
## Install
```sh
npm install p-retry
```
## Usage
```js
import pRetry, {AbortError} from 'p-retry';
import fetch from 'node-fetch';
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 maximum number of retries is reached. It then rejects with the last rejection reason.
It does not retry on most `TypeError`'s, with the exception of network errors. This is done on a best case basis as different browsers have different [messages](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#Checking_that_the_fetch_was_successful) to indicate this. See [whatwg/fetch#526 (comment)](https://github.com/whatwg/fetch/issues/526#issuecomment-554604080)
#### input
Type: `Function`
Receives the current attempt number as the first argument and is expected to return a `Promise` or any value.
#### options
Type: `object`
Options are passed to the [`retry`](https://github.com/tim-kos/node-retry#retryoperationoptions) module.
##### onFailedAttempt(error)
Type: `Function`
Callback invoked on each retry. Receives the error thrown by `input` as the first argument with properties `attemptNumber` and `retriesLeft` which indicate the current attempt number and the number of attempts left, respectively.
```js
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 => {
console.log(`Attempt ${error.attemptNumber} failed. There are ${error.retriesLeft} retries left.`);
// 1st request => Attempt 1 failed. There are 4 retries left.
// 2nd request => Attempt 2 failed. There are 3 retries left.
// …
},
retries: 5
});
console.log(result);
```
The `onFailedAttempt` function can return a promise. For example, you can do some async logging:
```js
import pRetry from 'p-retry';
import logger from './some-logger';
const run = async () => { };
const result = await pRetry(run, {
onFailedAttempt: async error => {
await logger.log(error);
}
});
```
If the `onFailedAttempt` function throws, all retries will be aborted and the original promise will reject with the thrown error.
##### shouldRetry(error)
Type: `Function`
Decide if a retry should occur based on the error. Returning true triggers a retry, false aborts with the error.
It is not called for `TypeError` (except network errors) and `AbortError`.
```js
import pRetry from 'p-retry';
const run = async () => { };
const result = await pRetry(run, {
shouldRetry: error => !(error instanceof CustomError);
});
```
In the example above, the operation will be retried unless the error is an instance of `CustomError`.
##### signal
Type: [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal)
You can abort retrying using [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController).
```js
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'
}
```
### AbortError(message)
### AbortError(error)
Abort retrying and reject the promise.
### 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:
```js
import pRetry from 'p-retry';
const run = async emoji => {
// …
};
// Without arguments
await pRetry(run, {retries: 5});
// With arguments
await pRetry(() => run('🦄'), {retries: 5});
```
## Related
- [p-timeout](https://github.com/sindresorhus/p-timeout) - Timeout a promise after a specified amount of time
- [More…](https://github.com/sindresorhus/promise-fun)