feat: complete Ancestor quote bot with production-ready Docker support

This commit is contained in:
unfunny
2026-09-13 14:14:38 -04:00
parent f2016da05a
commit fe7351a7dc
1145 changed files with 30890 additions and 97684 deletions

309
node_modules/p-retry/index.d.ts generated vendored
View File

@@ -1,267 +1,106 @@
export class AbortError extends Error {
import {OperationOptions} from 'retry';
declare class AbortErrorClass extends Error {
readonly name: 'AbortError';
readonly originalError: Error;
/**
Abort retrying and reject the promise. No callback functions will be called.
Abort retrying and reject the promise.
@param message - An error message or a custom error.
@param message - Error message or custom error.
*/
constructor(message: string | Error);
}
export type RetryContext = {
readonly error: Error;
readonly attemptNumber: number;
readonly retriesLeft: number;
readonly retriesConsumed: number;
declare namespace pRetry {
interface FailedAttemptError extends Error {
readonly attemptNumber: number;
readonly retriesLeft: number;
}
interface Options extends OperationOptions {
/**
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 = require('p-retry');
import delay = require('delay');
const run = async () => { ... };
(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>;
}
type AbortError = AbortErrorClass;
}
declare const pRetry: {
/**
The delay in milliseconds before the next retry attempt.
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.
This is calculated based on `minTimeout`, `factor`, `maxTimeout`, and `randomize` options.
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)
Note: The actual delay may be shorter if it would exceed `maxRetryTime`.
This is `0` when the retry is skipped or when no retry will occur based on the checks completed before the current callback runs.
*/
readonly retryDelay: number;
};
export type Options = {
/**
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`.
The function is not called on `AbortError`.
@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 from 'p-retry';
import pRetry = require('p-retry');
import fetch from 'node-fetch';
const run = async () => {
const response = await fetch('https://sindresorhus.com/unicorn');
if (!response.ok) {
throw new Error(response.statusText);
// Abort retrying if the resource doesn't exist
if (response.status === 404) {
throw new pRetry.AbortError(response.statusText);
}
return response.json();
return response.blob();
};
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
});
(async () => {
console.log(await pRetry(run, {retries: 5}));
console.log(result);
```
// 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
});
The `onFailedAttempt` function can return a promise. For example, to add a [delay](https://github.com/sindresorhus/delay):
@example
```
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);
}
});
```
If the `onFailedAttempt` function throws, all retries will be aborted and the original promise will reject with the thrown error.
*/
readonly onFailedAttempt?: (context: RetryContext) => void | Promise<void>;
/**
Decide if a retry should occur based on the 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.
@example
```
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`.
If the `shouldRetry` function throws, all retries will be aborted and the original promise will reject with the thrown error.
*/
readonly shouldRetry?: (context: RetryContext) => boolean | Promise<boolean>;
/**
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`.
@example
```
import pRetry from 'p-retry';
const run = async () => { … };
const result = await pRetry(run, {
retries: 2,
shouldConsumeRetry: ({error, retriesLeft}) => {
console.log(`Retries left: ${retriesLeft}`);
return !(error instanceof RateLimitError);
},
});
```
In the example above, `RateLimitError`s will not decrement the available `retries`.
If the `shouldConsumeRetry` function throws, all retries will be aborted and the original promise will reject with the thrown error.
*/
readonly shouldConsumeRetry?: (context: RetryContext) => boolean | Promise<boolean>;
/**
The maximum amount of times to retry the operation. Must be a non-negative integer or `Infinity`.
@default 10
*/
readonly retries?: number;
/**
The exponential factor to use.
@default 2
*/
readonly factor?: number;
/**
The number of milliseconds before starting the first retry.
Set this to `0` to retry immediately with no delay.
@default 1000
*/
readonly minTimeout?: number;
/**
The maximum number of milliseconds between two retries.
@default Infinity
*/
readonly maxTimeout?: number;
/**
Randomizes the timeouts by multiplying with a factor between 1 and 2.
@default false
*/
readonly randomize?: boolean;
/**
The maximum time (in milliseconds) that the retried operation is allowed to run.
@default Infinity
Measured with a monotonic clock (`performance.now()`) so system clock adjustments do not affect the limit.
*/
readonly maxRetryTime?: number;
/**
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'
}
console.log(result);
})();
```
*/
readonly signal?: AbortSignal | undefined;
<T>(
input: (attemptCount: number) => PromiseLike<T> | T,
options?: pRetry.Options
): Promise<T>;
/**
Prevents retry timeouts from keeping the process alive.
AbortError: typeof AbortErrorClass;
Only affects platforms with a `.unref()` method on timeouts, such as Node.js.
@default false
*/
readonly unref?: boolean;
// TODO: remove this in the next major version
default: typeof pRetry;
};
/**
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)
Non-network `TypeError`s always abort retries, even if `shouldConsumeRetry` or `shouldRetry` would otherwise allow another attempt.
@param input - Receives the number of attempts as the first argument and is expected to return a `Promise` or any value.
@param options - Options for configuring the retry behavior.
@example
```
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}));
```
*/
export default function pRetry<T>(
input: (attemptNumber: number) => PromiseLike<T> | T,
options?: Options
): Promise<T>;
/**
Wrap a function so that each call is automatically retried on failure.
@example
```
import {makeRetriable} from 'p-retry';
const fetchWithRetry = makeRetriable(fetch, {retries: 5});
const response = await fetchWithRetry('https://sindresorhus.com/unicorn');
```
*/
export function makeRetriable<Arguments extends readonly unknown[], Result>(
function_: (...arguments_: Arguments) => PromiseLike<Result> | Result,
options?: Options
): (...arguments_: Arguments) => Promise<Result>;
export = pRetry;

298
node_modules/p-retry/index.js generated vendored
View File

@@ -1,52 +1,14 @@
import isNetworkError from 'is-network-error';
'use strict';
const retry = require('retry');
function validateRetries(retries) {
if (typeof retries === 'number') {
if (retries < 0) {
throw new TypeError('Expected `retries` to be a non-negative number.');
}
const networkErrorMsgs = [
'Failed to fetch', // Chrome
'NetworkError when attempting to fetch resource.', // Firefox
'The Internet connection appears to be offline.', // Safari
'Network request failed' // `cross-fetch`
];
if (Number.isNaN(retries)) {
throw new TypeError('Expected `retries` to be a valid number or Infinity, got NaN.');
}
if (Number.isFinite(retries) && !Number.isInteger(retries)) {
throw new TypeError('Expected `retries` to be a non-negative integer or Infinity.');
}
} else if (retries !== undefined) {
throw new TypeError('Expected `retries` to be a number or Infinity.');
}
}
function validateNumberOption(name, value, {min = 0, allowInfinity = false} = {}) {
if (value === undefined) {
return;
}
if (typeof value !== 'number' || Number.isNaN(value)) {
throw new TypeError(`Expected \`${name}\` to be a number${allowInfinity ? ' or Infinity' : ''}.`);
}
if (!allowInfinity && !Number.isFinite(value)) {
throw new TypeError(`Expected \`${name}\` to be a finite number.`);
}
if (value < min) {
throw new TypeError(`Expected \`${name}\` to be \u2265 ${min}.`);
}
}
function validateFunctionOption(name, value) {
if (value === undefined) {
return;
}
if (typeof value !== 'function') {
throw new TypeError(`Expected \`${name}\` to be a function.`);
}
}
export class AbortError extends Error {
class AbortError extends Error {
constructor(message) {
super();
@@ -63,209 +25,61 @@ export class AbortError extends Error {
}
}
function calculateDelay(retriesConsumed, options) {
const attempt = Math.max(1, retriesConsumed + 1);
const random = options.randomize ? (Math.random() + 1) : 1;
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);
let timeout = Math.round(random * options.minTimeout * (options.factor ** (attempt - 1)));
timeout = Math.min(timeout, options.maxTimeout);
error.attemptNumber = attemptNumber;
error.retriesLeft = retriesLeft;
return error;
};
return timeout;
}
const isNetworkError = errorMessage => networkErrorMsgs.includes(errorMessage);
function calculateRemainingTime(start, max) {
if (!Number.isFinite(max)) {
return max;
}
const pRetry = (input, options) => new Promise((resolve, reject) => {
options = {
onFailedAttempt: () => {},
retries: 10,
...options
};
return max - (performance.now() - start);
}
async function delayForRetry(delay, options) {
if (delay <= 0) {
return;
}
await new Promise((resolve, reject) => {
const onAbort = () => {
clearTimeout(timeoutToken);
options.signal?.removeEventListener('abort', onAbort);
reject(options.signal.reason);
};
const timeoutToken = setTimeout(() => {
options.signal?.removeEventListener('abort', onAbort);
resolve();
}, delay);
if (options.unref) {
timeoutToken.unref?.();
}
options.signal?.addEventListener('abort', onAbort, {once: true});
});
}
async function onAttemptFailure({error, attemptNumber, retriesConsumed, startTime, options}) {
const normalizedError = error instanceof Error
? error
: new TypeError(`Non-error was thrown: "${error}". You should only throw errors.`);
if (normalizedError instanceof AbortError) {
throw normalizedError.originalError;
}
const retriesLeft = Number.isFinite(options.retries)
? Math.max(0, options.retries - retriesConsumed)
: options.retries;
const maxRetryTime = options.maxRetryTime ?? Number.POSITIVE_INFINITY;
const delayTime = calculateDelay(retriesConsumed, options);
const remainingTimeBeforeCallbacks = calculateRemainingTime(startTime, maxRetryTime);
if (remainingTimeBeforeCallbacks <= 0) {
const context = Object.freeze({
error: normalizedError,
attemptNumber,
retriesLeft,
retriesConsumed,
retryDelay: 0,
});
await options.onFailedAttempt(context);
throw normalizedError;
}
const consumeRetryContext = Object.freeze({
error: normalizedError,
attemptNumber,
retriesLeft,
retriesConsumed,
retryDelay: retriesLeft > 0 ? delayTime : 0,
});
const consumeRetry = await options.shouldConsumeRetry(consumeRetryContext);
const effectiveDelay = consumeRetry && retriesLeft > 0 ? delayTime : 0;
const context = Object.freeze({
error: normalizedError,
attemptNumber,
retriesLeft,
retriesConsumed,
retryDelay: effectiveDelay,
});
await options.onFailedAttempt(context);
if (calculateRemainingTime(startTime, maxRetryTime) <= 0) {
throw normalizedError;
}
const remainingTime = calculateRemainingTime(startTime, maxRetryTime);
if (remainingTime <= 0 || retriesLeft <= 0) {
throw normalizedError;
}
if (normalizedError instanceof TypeError && !isNetworkError(normalizedError)) {
throw normalizedError;
}
if (!await options.shouldRetry(context)) {
throw normalizedError;
}
const remainingTimeAfterShouldRetry = calculateRemainingTime(startTime, maxRetryTime);
if (remainingTimeAfterShouldRetry <= 0) {
throw normalizedError;
}
if (!consumeRetry) {
options.signal?.throwIfAborted();
return false;
}
const finalDelay = Math.min(effectiveDelay, remainingTimeAfterShouldRetry);
options.signal?.throwIfAborted();
await delayForRetry(finalDelay, options);
options.signal?.throwIfAborted();
return true;
}
export default async function pRetry(input, options = {}) {
options = {...options};
validateRetries(options.retries);
if (Object.hasOwn(options, 'forever')) {
throw new Error('The `forever` option is no longer supported. For many use-cases, you can set `retries: Infinity` instead.');
}
options.retries ??= 10;
options.factor ??= 2;
options.minTimeout ??= 1000;
options.maxTimeout ??= Number.POSITIVE_INFINITY;
options.maxRetryTime ??= Number.POSITIVE_INFINITY;
options.randomize ??= false;
options.onFailedAttempt ??= () => {};
options.shouldRetry ??= () => true;
options.shouldConsumeRetry ??= () => true;
// Validate numeric options and normalize edge cases
validateFunctionOption('onFailedAttempt', options.onFailedAttempt);
validateFunctionOption('shouldRetry', options.shouldRetry);
validateFunctionOption('shouldConsumeRetry', options.shouldConsumeRetry);
validateNumberOption('factor', options.factor, {min: 0, allowInfinity: false});
validateNumberOption('minTimeout', options.minTimeout, {min: 0, allowInfinity: false});
validateNumberOption('maxTimeout', options.maxTimeout, {min: 0, allowInfinity: true});
validateNumberOption('maxRetryTime', options.maxRetryTime, {min: 0, allowInfinity: true});
// Treat non-positive factor as 1 to avoid zero backoff or negative behavior
if (!(options.factor > 0)) {
options.factor = 1;
}
options.signal?.throwIfAborted();
let attemptNumber = 0;
let retriesConsumed = 0;
const startTime = performance.now();
while (Number.isFinite(options.retries) ? retriesConsumed <= options.retries : true) {
attemptNumber++;
const operation = retry.operation(options);
operation.attempt(async attemptNumber => {
try {
options.signal?.throwIfAborted();
const result = await input(attemptNumber);
options.signal?.throwIfAborted();
return result;
resolve(await input(attemptNumber));
} catch (error) {
if (await onAttemptFailure({
error,
attemptNumber,
retriesConsumed,
startTime,
options,
})) {
retriesConsumed++;
if (!(error instanceof Error)) {
reject(new TypeError(`Non-error was thrown: "${error}". You should only throw errors.`));
return;
}
if (error instanceof AbortError) {
operation.stop();
reject(error.originalError);
} else if (error instanceof TypeError && !isNetworkError(error.message)) {
operation.stop();
reject(error);
} else {
decorateErrorWithCounts(error, attemptNumber, options);
try {
await options.onFailedAttempt(error);
} catch (error) {
reject(error);
return;
}
if (!operation.retry(error)) {
reject(operation.mainError());
}
}
}
}
});
});
// Should not reach here, but in case it does, throw an error
throw new Error('Retry attempts exhausted without throwing an error.');
}
module.exports = pRetry;
// TODO: remove this in the next major version
module.exports.default = pRetry;
export function makeRetriable(function_, options) {
return function (...arguments_) {
return pRetry(() => function_.apply(this, arguments_), options);
};
}
module.exports.AbortError = AbortError;

2
node_modules/p-retry/license generated vendored
View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (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:

30
node_modules/p-retry/package.json generated vendored
View File

@@ -1,23 +1,16 @@
{
"name": "p-retry",
"version": "8.0.1",
"version": "4.6.2",
"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"
"url": "sindresorhus.com"
},
"type": "module",
"exports": {
"types": "./index.d.ts",
"default": "./index.js"
},
"sideEffects": false,
"engines": {
"node": ">=22"
"node": ">=8"
},
"scripts": {
"test": "xo && ava && tsd"
@@ -46,18 +39,13 @@
"bluebird"
],
"dependencies": {
"is-network-error": "^1.3.0"
"@types/retry": "0.12.0",
"retry": "^0.13.1"
},
"devDependencies": {
"ava": "^6.4.1",
"delay": "^7.0.0",
"execa": "^9.6.1",
"tsd": "^0.33.0",
"xo": "^1.2.3"
},
"xo": {
"rules": {
"no-await-in-loop": "off"
}
"ava": "^2.4.0",
"delay": "^4.1.0",
"tsd": "^0.10.0",
"xo": "^0.25.3"
}
}

271
node_modules/p-retry/readme.md generated vendored
View File

@@ -6,62 +6,61 @@ It does exponential backoff and supports custom retry strategies for failed oper
## Install
```sh
npm install p-retry
```
$ npm install p-retry
```
## Usage
```js
import pRetry, {AbortError} from 'p-retry';
const pRetry = require('p-retry');
const fetch = require('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);
throw new pRetry.AbortError(response.statusText);
}
return response.blob();
};
console.log(await pRetry(run, {retries: 5}));
(async () => {
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.
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.
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)
Non-network `TypeError`s 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.
Receives the current attempt number as the first argument and is expected to return a `Promise` or any value.
#### options
Type: `object`
##### onFailedAttempt(context)
Options are passed to the [`retry`](https://github.com/tim-kos/node-retry#retryoperationoptions) module.
##### onFailedAttempt(error)
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.
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');
@@ -72,247 +71,77 @@ const run = async () => {
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
});
(async () => {
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);
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](https://github.com/sindresorhus/delay):
The `onFailedAttempt` function can return a promise. For example, you can do some async logging:
```js
import pRetry from 'p-retry';
import delay from 'delay';
const pRetry = require('p-retry');
const logger = require('./some-logger');
const run = async () => { … };
const result = await pRetry(run, {
onFailedAttempt: async () => {
console.log('Waiting for 1 second before retrying');
await delay(1000);
}
});
(async () => {
const result = await pRetry(run, {
onFailedAttempt: async error => {
await logger.log(error);
}
});
})();
```
##### shouldRetry(context)
If the `onFailedAttempt` function throws, all retries will be aborted and the original promise will reject with the thrown error.
Type: `Function`
### pRetry.AbortError(message)
### pRetry.AbortError(error)
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.
```js
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.
```js
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, `RateLimitError`s will not decrement the available `retries`.
##### retries
Type: `number`\
Default: `10`
The maximum amount of times to retry the operation. Must be a non-negative integer or `Infinity`.
##### 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`](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'
}
```
##### 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.
```js
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.
Abort retrying and reject the promise.
### message
Type: `string`
An error message.
Error message.
### error
Type: `Error`
A custom error.
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 pRetry = require('p-retry');
const run = async emoji => {
// …
};
// Without arguments
await pRetry(run, {retries: 5});
(async () => {
// Without arguments
await pRetry(run, {retries: 5});
// With 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](https://nodejs.org/api/test.html#class-mocktimers) or a package like [`sinon`](https://github.com/sinonjs/sinon).
### How do I stop retries when the process receives SIGINT (Ctrl+C)?
Use an [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) to signal cancellation on SIGINT, and pass its `signal` to `pRetry`:
```js
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
- [p-timeout](https://github.com/sindresorhus/p-timeout) - Timeout a promise after a specified amount of time