All files / src/common retry.ts

95.83% Statements 23/24
90% Branches 9/10
100% Functions 2/2
95.65% Lines 22/23

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 613x 3x 3x                         3x     3x     4x         4x 4x 4x       4x 10x 10x   8x 8x 8x             8x 6x     6x 6x 6x   6x       2x      
import { loggerFactory } from "../logger";
import { stringifyError } from "./errors";
import { sleep } from "./time";
 
export type RetryConfig<T extends (...args: unknown[]) => Promise<unknown>> = {
  fn: T;
  fnName?: string;
  maxRetries: number;
  waitBetweenMs?: number;
  disableLog?: boolean;
  backOff?: {
    backOffBase: number;
  };
};
 
const logger = loggerFactory("retry");
 
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function retry<T extends (...args: any[]) => Promise<unknown>>(
  config: RetryConfig<T>
) {
  Iif (config.maxRetries === 0) {
    throw new Error(
      `Setting 'config.maxRetries' to 0 will never call the underlying function`
    );
  }
  return async (...args: Parameters<T>): Promise<Awaited<ReturnType<T>>> => {
    const fnName = config.fnName ?? config.fn.name;
    const error = new AggregateError(
      [],
      `Retry failed after ${config.maxRetries} attempts of ${fnName}`
    );
    for (let i = 0; i < config.maxRetries; i++) {
      try {
        return await (config.fn(...args) as Promise<ReturnType<T>>);
      } catch (e) {
        error.errors.push(e);
        if (!config.disableLog) {
          logger.log(
            `Retry ${i + 1}/${config.maxRetries}; Function ${fnName} failed.`,
            stringifyError(e)
          );
        }
 
        // don't wait in the last iteration
        if (config.waitBetweenMs && i !== config.maxRetries - 1) {
          const sleepTimeBackOffMultiplier = config.backOff
            ? Math.pow(config.backOff.backOffBase, i)
            : 1;
          const sleepTime = config.waitBetweenMs * sleepTimeBackOffMultiplier;
          if (!config.disableLog) {
            logger.log(`Waiting ${sleepTime / 1000} s. for the next retry...`);
          }
          await sleep(sleepTime);
        }
      }
    }
    throw error;
  };
}