UNPKG

@geersch/retry

Version:

Backoff strategies to use when retrying a function after a given delay.

42 lines 2.14 kB
import { defer, firstValueFrom, fromEvent, Observable, retry as retryOperator, switchMap, takeUntil, tap, throwError, timer, } from 'rxjs'; export async function retry(operation, backoffStrategy, options = {}) { let attempt = 1; return firstValueFrom(passRetryOperatorToPipe(defer(async () => operation(attempt)).pipe(tap({ error: () => (attempt += 1), })), backoffStrategy, options)); } function createTimer(due, signal, error) { return signal ? timer(due).pipe(takeUntil(fromEvent(signal, 'abort').pipe(switchMap(() => throwError(() => error))))) : timer(due); } export function passRetryOperatorToPipe(observable, backoffStrategy, { abortRetry, maxDelay = 30000, maxRetries = 5, scaleFactor = 1, signal, unrecoverableErrors = [] }) { if (scaleFactor <= 0) { throw new TypeError(`Expected 'scaleFactor' to be a positive number greater than zero, got ${scaleFactor}.`); } const strategy = typeof backoffStrategy === 'function' ? new backoffStrategy() : backoffStrategy; const generator = strategy.getGenerator(maxRetries); const abortSignal = typeof signal === 'function' ? signal() : signal; return observable.pipe(retryOperator({ count: maxRetries, delay: (error, retryCount) => { if (abortSignal?.aborted) { return throwError(() => error); } const isUnrecoverable = unrecoverableErrors.some((errorConstructor) => error instanceof errorConstructor); if (isUnrecoverable || (abortRetry?.(error, retryCount) ?? false)) { return throwError(() => error); } const { value, done } = generator.next(); if (done) { return throwError(() => new Error(`The backoff strategy did not yield a delay for retry attempt ${retryCount}.`)); } let delay = value * scaleFactor; if (delay > maxDelay) { delay = maxDelay; } return abortSignal ? createTimer(delay, abortSignal, error) : createTimer(delay); }, })); } //# sourceMappingURL=retry.js.map