UNPKG

retryable-operation

Version:

a simple package that allow executing retryable operation and providing retry options

109 lines (95 loc) 2.88 kB
import { Result } from "./result"; export interface RetryOptions<T = unknown, E = unknown> { fn: AttemptedFunction<T>; args?: any[]; reties?: number; factor?: number; shouldRetry?: (err: E, attempts: number) => boolean; executor?: Executor<T>; maxDelay?: number; minDelay?: number; } export type AttemptedFunction<T> = (...args: any[]) => T | Promise<T>; export type Executor<T> = (fn: AttemptedFunction<T>) => Promise<T>; export type OperationResult<T, E> = Result<T, E> & { readonly operationRef: RetryableOperation<T, E>; }; export class RetryableOperation<T = unknown, E = unknown> { private fn: AttemptedFunction<T>; private args: any[] = []; private _errors: E[] = []; private _attempts = 1; private shouldRetry = (err: E, attempts: number) => true; private retries = 3; private factor = 2; private timeout: number; private maxDelay = 60 * 1000 * 1; private minDelay = 1000; private _executor?: Executor<T>; constructor(_options: RetryOptions<T>) { this.retries = _options.reties ?? this.retries; this.shouldRetry = _options.shouldRetry || this.shouldRetry; this.fn = _options.fn; this.factor = _options.factor ?? this.factor; this.args = _options.args ?? this.args; this._executor = _options.executor; this.timeout = this.minDelay; this.maxDelay = _options.maxDelay ?? this.maxDelay; this.minDelay = _options.minDelay ?? this.minDelay; } attempt(): Promise<OperationResult<T, E>> { return new Promise(async (resolve, reject) => { try { const result = this._executor ? await this._executor(this.fn) : await this.fn(...this.args); resolve({ operationRef: this, ...Result.Ok(result) }); } catch (error) { this._errors.push(error as E); if ( this.shouldRetry(error as E, this._attempts) && this._attempts < this.retries ) { this.timeout = Math.floor( Math.min( Math.random() * this.minDelay * Math.pow(this.factor, this.attempts), this.maxDelay ) ); setTimeout(() => { this._attempts++; resolve(this.attempt()); }, this.timeout); } else resolve({ operationRef: this, ...Result.Err(error as E) }); } }); } setExecutor(executor: Executor<T>) { this._executor = executor; return this; } setArgs(...args: any[]) { this.args = args; return this; } set executor(executor: Executor<T>) { this._executor = executor; } get options() { return { retries: this.retries, factor: this.factor, args: this.args, maxDelay: this.maxDelay, minDelay: this.minDelay, }; } get attempts() { return this._attempts; } get errors() { return this._errors; } }