UNPKG

@daisugi/kintsugi

Version:

Kintsugi is a set of utilities to help build a fault tolerant services.

104 lines (94 loc) 2.49 kB
import type { AnzenAnyResult, AnzenResultFn, } from '@daisugi/anzen'; import { Ayamari } from '@daisugi/ayamari'; import { randomBetween } from './random_between.js'; import { waitFor } from './wait_for.js'; interface WithRetryOpts { firstDelayMs?: number; maxDelayMs?: number; timeFactor?: number; maxRetries?: number; calculateRetryDelayMs?( firstDelayMs: number, maxDelayMs: number, timeFactor: number, retryNumber: number, ): number; shouldRetry?( response: any, retryNumber: number, maxRetries: number, ): boolean; } const defaultFirstDelayMs = 200; const defaultMaxDelayMs = 600; const defaultTimeFactor = 2; const defaultMaxRetries = 3; export function calculateRetryDelayMs( firstDelayMs: number, maxDelayMs: number, timeFactor: number, retryNumber: number, ) { const delayMs = Math.min( maxDelayMs, firstDelayMs * timeFactor ** retryNumber, ); /** Full jitter https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/ */ const delayWithJitterMs = randomBetween(0, delayMs); return delayWithJitterMs; } export function shouldRetry( response: any, retryNumber: number, maxRetries: number, ) { if (response.isFailure) { if ( response.getError().code === Ayamari.errCode.CircuitSuspended ) { return false; } if (retryNumber < maxRetries) { return true; } } return false; } export function withRetry( fn: AnzenResultFn<any, any>, opts: WithRetryOpts = {}, ) { const firstDelayMs = opts.firstDelayMs || defaultFirstDelayMs; const maxDelayMs = opts.maxDelayMs || defaultMaxDelayMs; const timeFactor = opts.timeFactor || defaultTimeFactor; const maxRetries = opts.maxRetries || defaultMaxRetries; const _calculateRetryDelayMs = opts.calculateRetryDelayMs || calculateRetryDelayMs; const _shouldRetry = opts.shouldRetry || shouldRetry; async function fnWithRetry( this: unknown, fn: AnzenResultFn<any, any>, args: any[], retryNumber: number, ): Promise<AnzenAnyResult<any, any>> { const response = await fn.call(this, args); if (_shouldRetry(response, retryNumber, maxRetries)) { await waitFor( _calculateRetryDelayMs( firstDelayMs, maxDelayMs, timeFactor, retryNumber, ), ); return fnWithRetry(fn, args, retryNumber + 1); } return response; } return (...args: any[]) => fnWithRetry(fn, args, 0); }