initial
This commit is contained in:
100
ts/helpers/helpers.retry.ts
Normal file
100
ts/helpers/helpers.retry.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import type { IRetryOptions } from '../interfaces/index.js';
|
||||
|
||||
const defaultRetryOptions: Required<IRetryOptions> = {
|
||||
maxRetries: 5,
|
||||
baseDelay: 1000,
|
||||
maxDelay: 16000,
|
||||
multiplier: 2,
|
||||
jitter: true,
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculates the delay for a retry attempt using exponential backoff
|
||||
*/
|
||||
function calculateDelay(
|
||||
attempt: number,
|
||||
options: Required<IRetryOptions>
|
||||
): number {
|
||||
const delay = Math.min(
|
||||
options.baseDelay * Math.pow(options.multiplier, attempt),
|
||||
options.maxDelay
|
||||
);
|
||||
|
||||
if (options.jitter) {
|
||||
// Add random jitter of +/- 25%
|
||||
const jitterRange = delay * 0.25;
|
||||
return delay + (Math.random() * jitterRange * 2 - jitterRange);
|
||||
}
|
||||
|
||||
return delay;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a function with retry logic using exponential backoff
|
||||
*
|
||||
* @param fn - The async function to execute
|
||||
* @param options - Retry configuration options
|
||||
* @returns The result of the function
|
||||
* @throws The last error if all retries fail
|
||||
*/
|
||||
export async function withRetry<T>(
|
||||
fn: () => Promise<T>,
|
||||
options?: IRetryOptions
|
||||
): Promise<T> {
|
||||
const opts: Required<IRetryOptions> = { ...defaultRetryOptions, ...options };
|
||||
let lastError: Error | undefined;
|
||||
|
||||
for (let attempt = 0; attempt <= opts.maxRetries; attempt++) {
|
||||
try {
|
||||
return await fn();
|
||||
} catch (error) {
|
||||
lastError = error instanceof Error ? error : new Error(String(error));
|
||||
|
||||
if (attempt === opts.maxRetries) {
|
||||
break;
|
||||
}
|
||||
|
||||
const delay = calculateDelay(attempt, opts);
|
||||
await plugins.smartdelay.delayFor(delay);
|
||||
}
|
||||
}
|
||||
|
||||
throw lastError ?? new Error('Retry failed with unknown error');
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a retryable version of an async function
|
||||
*
|
||||
* @param fn - The async function to wrap
|
||||
* @param options - Retry configuration options
|
||||
* @returns A wrapped function that retries on failure
|
||||
*/
|
||||
export function createRetryable<TArgs extends unknown[], TResult>(
|
||||
fn: (...args: TArgs) => Promise<TResult>,
|
||||
options?: IRetryOptions
|
||||
): (...args: TArgs) => Promise<TResult> {
|
||||
return (...args: TArgs) => withRetry(() => fn(...args), options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retry decorator for class methods
|
||||
* Note: Use as a wrapper function since TC39 decorators have different semantics
|
||||
*/
|
||||
export function retryMethod<T extends Record<string, unknown>>(
|
||||
target: T,
|
||||
methodName: keyof T & string,
|
||||
options?: IRetryOptions
|
||||
): void {
|
||||
const originalMethod = target[methodName];
|
||||
if (typeof originalMethod !== 'function') {
|
||||
throw new Error(`${methodName} is not a function`);
|
||||
}
|
||||
|
||||
target[methodName] = createRetryable(
|
||||
originalMethod.bind(target) as (...args: unknown[]) => Promise<unknown>,
|
||||
options
|
||||
) as T[typeof methodName];
|
||||
}
|
||||
|
||||
export { defaultRetryOptions };
|
||||
Reference in New Issue
Block a user