| 1234567891011121314151617181920212223242526272829303132333435363738394041 |
- export interface RetryOptions {
- attempts?: number
- delay?: number
- factor?: number
- maxDelay?: number
- retryIf?: (error: unknown) => boolean
- }
- const TRANSIENT_MESSAGES = [
- "load failed",
- "network connection was lost",
- "network request failed",
- "failed to fetch",
- "econnreset",
- "econnrefused",
- "etimedout",
- "socket hang up",
- ]
- function isTransientError(error: unknown): boolean {
- if (!error) return false
- const message = String(error instanceof Error ? error.message : error).toLowerCase()
- return TRANSIENT_MESSAGES.some((m) => message.includes(m))
- }
- export async function retry<T>(fn: () => Promise<T>, options: RetryOptions = {}): Promise<T> {
- const { attempts = 3, delay = 500, factor = 2, maxDelay = 10000, retryIf = isTransientError } = options
- let lastError: unknown
- for (let attempt = 0; attempt < attempts; attempt++) {
- try {
- return await fn()
- } catch (error) {
- lastError = error
- if (attempt === attempts - 1 || !retryIf(error)) throw error
- const wait = Math.min(delay * Math.pow(factor, attempt), maxDelay)
- await new Promise((resolve) => setTimeout(resolve, wait))
- }
- }
- throw lastError
- }
|