An in-browser wisp.place site explorer
at main 99 lines 2.4 kB view raw
1/** 2 * Retry utility for network operations with exponential backoff 3 */ 4 5export interface RetryOptions { 6 maxAttempts?: number; 7 initialDelay?: number; 8 maxDelay?: number; 9 backoffFactor?: number; 10 shouldRetry?: (error: unknown) => boolean; 11} 12 13const defaultRetryOptions: Required<RetryOptions> = { 14 maxAttempts: 3, 15 initialDelay: 1000, 16 maxDelay: 10000, 17 backoffFactor: 2, 18 shouldRetry: (error: unknown) => { 19 // Retry on network errors and 5xx errors 20 if (error instanceof TypeError) { 21 // Network errors (fetch failed) 22 return true; 23 } 24 if ( 25 error instanceof Error && 26 'status' in error && 27 typeof (error as { status: number }).status === 'number' 28 ) { 29 const status = (error as { status: number }).status; 30 return status >= 500 || status === 429; // 5xx or 429 (rate limit) 31 } 32 return false; 33 }, 34}; 35 36/** 37 * Sleep for a specified duration 38 */ 39function sleep(ms: number): Promise<void> { 40 return new Promise((resolve) => setTimeout(resolve, ms)); 41} 42 43/** 44 * Calculate delay with exponential backoff 45 */ 46function calculateDelay( 47 attempt: number, 48 options: Required<RetryOptions> 49): number { 50 const delay = options.initialDelay * Math.pow(options.backoffFactor, attempt); 51 return Math.min(delay, options.maxDelay); 52} 53 54/** 55 * Execute a function with retry logic 56 */ 57export async function withRetry<T>( 58 fn: () => Promise<T>, 59 options: RetryOptions = {} 60): Promise<T> { 61 const opts = { ...defaultRetryOptions, ...options }; 62 63 let lastError: unknown; 64 65 for (let attempt = 0; attempt < opts.maxAttempts; attempt++) { 66 try { 67 return await fn(); 68 } catch (error) { 69 lastError = error; 70 71 const isLastAttempt = attempt === opts.maxAttempts - 1; 72 const shouldRetry = !isLastAttempt && opts.shouldRetry(error); 73 74 if (!shouldRetry) { 75 throw error; 76 } 77 78 const delay = calculateDelay(attempt, opts); 79 console.warn( 80 `Retry attempt ${attempt + 1}/${opts.maxAttempts} after ${delay}ms`, 81 { error } 82 ); 83 84 await sleep(delay); 85 } 86 } 87 88 throw lastError; 89} 90 91/** 92 * Create a retryable version of a function 93 */ 94export function retryable<T extends Array<unknown>, U>( 95 fn: (...args: T) => Promise<U>, 96 options: RetryOptions = {} 97): (...args: T) => Promise<U> { 98 return (...args: T) => withRetry(() => fn(...args), options); 99}