/** * Async utility functions for SmartProxy * Provides non-blocking alternatives to synchronous operations */ /** * Delays execution for the specified number of milliseconds * Non-blocking alternative to busy wait loops * @param ms - Number of milliseconds to delay * @returns Promise that resolves after the delay */ export async function delay(ms: number): Promise { return new Promise(resolve => setTimeout(resolve, ms)); } /** * Retry an async operation with exponential backoff * @param fn - The async function to retry * @param options - Retry options * @returns The result of the function or throws the last error */ export async function retryWithBackoff( fn: () => Promise, options: { maxAttempts?: number; initialDelay?: number; maxDelay?: number; factor?: number; onRetry?: (attempt: number, error: Error) => void; } = {} ): Promise { const { maxAttempts = 3, initialDelay = 100, maxDelay = 10000, factor = 2, onRetry } = options; let lastError: Error | null = null; let currentDelay = initialDelay; for (let attempt = 1; attempt <= maxAttempts; attempt++) { try { return await fn(); } catch (error: any) { lastError = error; if (attempt === maxAttempts) { throw error; } if (onRetry) { onRetry(attempt, error); } await delay(currentDelay); currentDelay = Math.min(currentDelay * factor, maxDelay); } } throw lastError || new Error('Retry failed'); } /** * Execute an async operation with a timeout * @param fn - The async function to execute * @param timeoutMs - Timeout in milliseconds * @param timeoutError - Optional custom timeout error * @returns The result of the function or throws timeout error */ export async function withTimeout( fn: () => Promise, timeoutMs: number, timeoutError?: Error ): Promise { const timeoutPromise = new Promise((_, reject) => { setTimeout(() => { reject(timeoutError || new Error(`Operation timed out after ${timeoutMs}ms`)); }, timeoutMs); }); return Promise.race([fn(), timeoutPromise]); } /** * Run multiple async operations in parallel with a concurrency limit * @param items - Array of items to process * @param fn - Async function to run for each item * @param concurrency - Maximum number of concurrent operations * @returns Array of results in the same order as input */ export async function parallelLimit( items: T[], fn: (item: T, index: number) => Promise, concurrency: number ): Promise { const results: R[] = new Array(items.length); const executing: Set> = new Set(); for (let i = 0; i < items.length; i++) { const promise = fn(items[i], i).then(result => { results[i] = result; executing.delete(promise); }); executing.add(promise); if (executing.size >= concurrency) { await Promise.race(executing); } } await Promise.all(executing); return results; } /** * Debounce an async function * @param fn - The async function to debounce * @param delayMs - Delay in milliseconds * @returns Debounced function with cancel method */ export function debounceAsync Promise>( fn: T, delayMs: number ): T & { cancel: () => void } { let timeoutId: NodeJS.Timeout | null = null; let lastPromise: Promise | null = null; const debounced = ((...args: Parameters) => { if (timeoutId) { clearTimeout(timeoutId); } lastPromise = new Promise((resolve, reject) => { timeoutId = setTimeout(async () => { timeoutId = null; try { const result = await fn(...args); resolve(result); } catch (error) { reject(error); } }, delayMs); }); return lastPromise; }) as any; debounced.cancel = () => { if (timeoutId) { clearTimeout(timeoutId); timeoutId = null; } }; return debounced as T & { cancel: () => void }; } /** * Create a mutex for ensuring exclusive access to a resource */ export class AsyncMutex { private queue: Array<() => void> = []; private locked = false; async acquire(): Promise<() => void> { if (!this.locked) { this.locked = true; return () => this.release(); } return new Promise<() => void>(resolve => { this.queue.push(() => { resolve(() => this.release()); }); }); } private release(): void { const next = this.queue.shift(); if (next) { next(); } else { this.locked = false; } } async runExclusive(fn: () => Promise): Promise { const release = await this.acquire(); try { return await fn(); } finally { release(); } } } /** * Circuit breaker for protecting against cascading failures */ export class CircuitBreaker { private failureCount = 0; private lastFailureTime = 0; private state: 'closed' | 'open' | 'half-open' = 'closed'; constructor( private options: { failureThreshold: number; resetTimeout: number; onStateChange?: (state: 'closed' | 'open' | 'half-open') => void; } ) {} async execute(fn: () => Promise): Promise { if (this.state === 'open') { if (Date.now() - this.lastFailureTime > this.options.resetTimeout) { this.setState('half-open'); } else { throw new Error('Circuit breaker is open'); } } try { const result = await fn(); this.onSuccess(); return result; } catch (error) { this.onFailure(); throw error; } } private onSuccess(): void { this.failureCount = 0; if (this.state !== 'closed') { this.setState('closed'); } } private onFailure(): void { this.failureCount++; this.lastFailureTime = Date.now(); if (this.failureCount >= this.options.failureThreshold) { this.setState('open'); } } private setState(state: 'closed' | 'open' | 'half-open'): void { if (this.state !== state) { this.state = state; if (this.options.onStateChange) { this.options.onStateChange(state); } } } isOpen(): boolean { return this.state === 'open'; } getState(): 'closed' | 'open' | 'half-open' { return this.state; } recordSuccess(): void { this.onSuccess(); } recordFailure(): void { this.onFailure(); } }