/** * Retry policy is provided with an attempt index and the latest error that * was the reason for the retry attempt. The policy can apply any delays * necessary, and must resolve with a boolean indicating whether further retry * attempts should be made. */ export type Retry = (context: RetryContext) => Promise; export interface RetryContext { /** * Current attempt index. Since retry policy is applied after retry attempts, * starts with 1 (attempt number 0 happens immediately). */ attempt: number; /** * The latest error that was the reason for the retry attempt to be made. */ latestError: any; } export const indefinitely = () => async (): Promise => true; export interface ExponentialBackoffOptions { minDelayMs?: number; maxDelayMs?: number; maxAttempts?: number; factor?: number; jitter?: number; } /** * Retry policy that allows a specified maximum number of attempts, each with * an exponentially growing delay after an attempt. */ export const exponentialBackoff = ({ minDelayMs = 1000, maxDelayMs = Number.POSITIVE_INFINITY, maxAttempts = 3, factor = 2, jitter = 0.1, }: ExponentialBackoffOptions = {}): Retry => ({ attempt }: RetryContext): Promise => { if (attempt > maxAttempts) { return Promise.resolve(false); } const delay = Math.min( minDelayMs * (factor ** (attempt - 1) + (Math.random() - 0.5) * jitter), maxDelayMs, ); return new Promise((resolve) => setTimeout(() => resolve(true), delay)); };