mirror of
https://github.com/wu736139669/hapi.git
synced 2026-10-13 20:09:55 +00:00
108 lines
3.7 KiB
TypeScript
108 lines
3.7 KiB
TypeScript
export async function delay(ms: number) {
|
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
}
|
|
|
|
export function exponentialBackoffDelay(currentFailureCount: number, minDelay: number, maxDelay: number, maxFailureCount: number) {
|
|
let maxDelayRet = minDelay + ((maxDelay - minDelay) / maxFailureCount) * Math.min(currentFailureCount, maxFailureCount);
|
|
return Math.round(Math.random() * maxDelayRet);
|
|
}
|
|
|
|
export type BackoffFunc = <T>(callback: () => Promise<T>) => Promise<T>;
|
|
|
|
export function createBackoff(
|
|
opts?: {
|
|
onError?: (e: any, failuresCount: number) => void,
|
|
minDelay?: number,
|
|
maxDelay?: number,
|
|
maxFailureCount?: number
|
|
}): BackoffFunc {
|
|
return async <T>(callback: () => Promise<T>): Promise<T> => {
|
|
let currentFailureCount = 0;
|
|
const minDelay = opts && opts.minDelay !== undefined ? opts.minDelay : 250;
|
|
const maxDelay = opts && opts.maxDelay !== undefined ? opts.maxDelay : 1000;
|
|
const maxFailureCount = opts && opts.maxFailureCount !== undefined ? opts.maxFailureCount : 50;
|
|
while (true) {
|
|
try {
|
|
return await callback();
|
|
} catch (e) {
|
|
if (currentFailureCount < maxFailureCount) {
|
|
currentFailureCount++;
|
|
}
|
|
if (opts && opts.onError) {
|
|
opts.onError(e, currentFailureCount);
|
|
}
|
|
let waitForRequest = exponentialBackoffDelay(currentFailureCount, minDelay, maxDelay, maxFailureCount);
|
|
await delay(waitForRequest);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
export let backoff = createBackoff();
|
|
|
|
/**
|
|
* Options for withRetry function
|
|
*/
|
|
export type RetryOptions = {
|
|
/** Maximum number of retry attempts. Default: unlimited */
|
|
maxAttempts?: number
|
|
/** Minimum delay between retries in ms. Default: 1000 */
|
|
minDelay?: number
|
|
/** Maximum delay between retries in ms. Default: 30000 */
|
|
maxDelay?: number
|
|
/** Function to determine if error is retryable. Default: retry all errors */
|
|
shouldRetry?: (error: unknown) => boolean
|
|
/** Callback when a retry is about to happen */
|
|
onRetry?: (error: unknown, attempt: number, nextDelayMs: number) => void
|
|
}
|
|
|
|
/**
|
|
* Execute a function with retry logic and exponential backoff
|
|
*
|
|
* Unlike createBackoff, this function:
|
|
* - Supports a shouldRetry predicate to skip non-retryable errors
|
|
* - Has sensible defaults for runner-style long-running processes
|
|
* - Uses clearer exponential backoff (2^n with jitter)
|
|
*/
|
|
export async function withRetry<T>(
|
|
fn: () => Promise<T>,
|
|
options?: RetryOptions
|
|
): Promise<T> {
|
|
const maxAttempts = options?.maxAttempts ?? Infinity
|
|
const minDelay = options?.minDelay ?? 1000
|
|
const maxDelay = options?.maxDelay ?? 30000
|
|
const shouldRetry = options?.shouldRetry ?? (() => true)
|
|
const onRetry = options?.onRetry
|
|
|
|
let attempt = 0
|
|
|
|
while (true) {
|
|
try {
|
|
return await fn()
|
|
} catch (error) {
|
|
attempt++
|
|
|
|
// Check if we should retry this error
|
|
if (!shouldRetry(error)) {
|
|
throw error
|
|
}
|
|
|
|
// Check if we've exceeded max attempts
|
|
if (attempt >= maxAttempts) {
|
|
throw error
|
|
}
|
|
|
|
// Calculate delay with exponential backoff and jitter
|
|
const exponentialDelay = minDelay * Math.pow(2, attempt - 1)
|
|
const cappedDelay = Math.min(exponentialDelay, maxDelay)
|
|
const jitter = Math.random() * 0.3 * cappedDelay // 0-30% jitter
|
|
const nextDelayMs = Math.round(cappedDelay + jitter)
|
|
|
|
if (onRetry) {
|
|
onRetry(error, attempt, nextDelayMs)
|
|
}
|
|
|
|
await delay(nextDelayMs)
|
|
}
|
|
}
|
|
} |