summaryrefslogtreecommitdiff
path: root/src/lib/utils/retry.ts
blob: 34bc73ba4a5664c8409461351588b7e399d911cc (plain)
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
export type RetryStrategyF = (retries: number) => number;

export const MAX_DEFAULT_RETRY_AMOUNT = 5;
export const WAIT_MS = 1_000;
export const RETRY_EXPONENT = 2;
export const RETRY_EXPONENTIAL_FACTOR = 1.1;
export const RETRY_JITTER_MAX = 3_000;

const waitFor = (ms: number) => new Promise((res) => setTimeout(res, ms));

const exponentialStrategyWithJitter: RetryStrategyF = (retries: number) =>
  WAIT_MS * Math.pow(RETRY_EXPONENT, RETRY_EXPONENTIAL_FACTOR * retries) +
  RETRY_JITTER_MAX * Math.random();

export const continueRetryUntilValidation = async <T>(
  promiseFn: () => Promise<T> | T | Promise<void> | void,
  validationFn: (x: T) => Promise<boolean> | boolean = (x: T) =>
    Promise.resolve(!!x),
  maxRetries = MAX_DEFAULT_RETRY_AMOUNT,
  waitTimeStrategy: RetryStrategyF = exponentialStrategyWithJitter,
  retries = 0,
  lastError: undefined | unknown = undefined
): Promise<T> => {
  if (retries >= maxRetries) {
    if (lastError) throw lastError;
    throw new Error("Exceeded maximum retry amount");
  }
  try {
    if (retries) await waitFor(waitTimeStrategy(retries));

    const res = await promiseFn();
    if (res && (await validationFn(res))) return res;

    throw new Error("Validation predicate unsuccessful");
  } catch (e: unknown) {
    return continueRetryUntilValidation(
      promiseFn,
      validationFn,
      maxRetries,
      waitTimeStrategy,
      retries + 1,
      e
    );
  }
};