retryable-operation
Version:
a simple package that allow executing retryable operation and providing retry options
109 lines (95 loc) • 2.88 kB
text/typescript
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;
}
}