Files
tools/core/stdlib/src/async/retry/index.ts
T

96 lines
2.2 KiB
TypeScript

import { tryIt } from '../tryIt';
import { sleep } from '../sleep';
import { isFunction } from '../../types';
export interface RetryOptions {
times?: number;
delay?: number | ((count: number) => number);
shouldRetry?: (error: Error, count: number) => boolean;
}
export type RetryFunction<Return> = (
args: {
count: number;
stop: (error: any) => void;
},
) => Promise<Return>;
class RetryEarlyExitError {
cause: any;
constructor(cause: any) {
this.cause = cause;
}
}
/**
* @name retry
* @category Async
* @description Retries a function a specified number of times with a delay between each retry
*
* @param {Promise<unknown>} fn - The function to retry
* @param {RetryOptions} options - The options for the retry
* @returns {Promise<unknown>} - The result of the function
*
* @example
* const result = await retry(() => {
* return fetch('https://jsonplaceholder.typicode.com/todos/1')
* .then(response => response.json())
* });
*
* @example
* const result = await retry(() => {
* return fetch('https://jsonplaceholder.typicode.com/todos/1')
* .then(response => response.json())
* }, { times: 3, delay: 1000 });
*
* @since 0.0.8
*/
export async function retry<Return>(
fn: RetryFunction<Return>,
options: RetryOptions = {},
): Promise<Return> {
const {
times = 2,
delay = 0,
shouldRetry,
} = options;
const wrappedFn = tryIt(fn);
const delayFn = isFunction(delay) ? delay : null;
const delayMs = delayFn ? 0 : delay as number;
const stop = (error?: any): never => {
throw new RetryEarlyExitError(error);
};
let lastError: Error | null = null;
let count = 1;
while (count <= times) {
const { error, data } = await wrappedFn({ count, stop });
if (!error)
return data;
if (error instanceof RetryEarlyExitError)
throw error.cause;
if (shouldRetry && !shouldRetry(error, count))
throw error;
lastError = error;
count++;
// Don't delay after the last attempt
if (count <= times) {
const ms = delayFn ? delayFn(count) : delayMs;
if (ms > 0)
await sleep(ms);
}
}
// eslint-disable-next-line eslint/no-throw-literal
throw lastError!;
}