UNPKG

@push.rocks/webrequest

Version:

Modern, fetch-compatible web request library with intelligent HTTP caching, retry strategies, and fault tolerance.

148 lines 11.7 kB
/** * Retry manager for handling request retries */ import * as plugins from '../webrequest.plugins.js'; import { getBackoffCalculator, addJitter } from './retry.strategies.js'; export class RetryManager { constructor(options = {}) { this.options = { maxAttempts: options.maxAttempts ?? 3, backoff: options.backoff ?? 'exponential', initialDelay: options.initialDelay ?? 1000, maxDelay: options.maxDelay ?? 30000, retryOn: options.retryOn ?? [408, 429, 500, 502, 503, 504], onRetry: options.onRetry ?? (() => { }), }; } /** * Execute a request with retry logic */ async execute(executeFn, shouldRetryFn) { let lastError; let lastResponse; for (let attempt = 1; attempt <= this.options.maxAttempts; attempt++) { try { const result = await executeFn(); // Check if result is a Response and if we should retry based on status if (result instanceof Response) { if (this.shouldRetryResponse(result)) { lastResponse = result; // If this is the last attempt, return the failed response if (attempt === this.options.maxAttempts) { return result; } // Calculate delay and retry const delay = this.calculateDelay(attempt); this.options.onRetry(attempt, new Error(`HTTP ${result.status}`), delay); await this.delay(delay); continue; } } // Success return result; } catch (error) { lastError = error instanceof Error ? error : new Error(String(error)); // Check if we should retry const shouldRetry = shouldRetryFn ? shouldRetryFn(error, attempt) : this.shouldRetryError(error); // If this is the last attempt or we shouldn't retry, throw if (attempt === this.options.maxAttempts || !shouldRetry) { throw lastError; } // Calculate delay and retry const delay = this.calculateDelay(attempt); this.options.onRetry(attempt, lastError, delay); await this.delay(delay); } } // This should never be reached, but TypeScript needs it throw lastError || new Error('Max retry attempts reached'); } /** * Execute with multiple fallback URLs */ async executeWithFallbacks(urls, requestInit, fetchFn) { if (urls.length === 0) { throw new Error('No URLs provided for fallback execution'); } let lastError; const failedUrls = []; for (const url of urls) { try { // Try the URL with retry logic const response = await this.execute(async () => { return await fetchFn(url, requestInit); }); // If successful (status < 400), return if (response.status < 400) { return response; } // If 4xx client error (except 408 timeout), don't try other URLs if (response.status >= 400 && response.status < 500 && response.status !== 408) { return response; } // Server error or timeout, try next URL failedUrls.push(url); lastError = new Error(`Request failed with status ${response.status}`); } catch (error) { failedUrls.push(url); lastError = error instanceof Error ? error : new Error(String(error)); } } // All URLs failed throw new Error(`All URLs failed: ${failedUrls.join(', ')}. Last error: ${lastError?.message || 'Unknown error'}`); } /** * Check if we should retry based on response status */ shouldRetryResponse(response) { const retryOn = this.options.retryOn; if (typeof retryOn === 'function') { return retryOn(response); } if (Array.isArray(retryOn)) { return retryOn.includes(response.status); } return false; } /** * Check if we should retry based on error */ shouldRetryError(error) { // Network errors should be retried if (error instanceof TypeError && error.message.includes('fetch')) { return true; } // Timeout errors should be retried if (error.name === 'AbortError' || error.message.includes('timeout')) { return true; } // If retryOn is a function, use it const retryOn = this.options.retryOn; if (typeof retryOn === 'function') { return retryOn(undefined, error); } return false; } /** * Calculate delay for next retry */ calculateDelay(attempt) { const calculator = getBackoffCalculator(this.options.backoff); const baseDelay = calculator.calculate(attempt, this.options.initialDelay, this.options.maxDelay); // Add jitter to prevent thundering herd return addJitter(baseDelay); } /** * Delay execution */ async delay(ms) { await plugins.smartdelay.delayFor(ms); } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmV0cnkubWFuYWdlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL3JldHJ5L3JldHJ5Lm1hbmFnZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7O0dBRUc7QUFFSCxPQUFPLEtBQUssT0FBTyxNQUFNLDBCQUEwQixDQUFDO0FBRXBELE9BQU8sRUFBRSxvQkFBb0IsRUFBRSxTQUFTLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQUV4RSxNQUFNLE9BQU8sWUFBWTtJQUd2QixZQUFZLFVBQXlCLEVBQUU7UUFDckMsSUFBSSxDQUFDLE9BQU8sR0FBRztZQUNiLFdBQVcsRUFBRSxPQUFPLENBQUMsV0FBVyxJQUFJLENBQUM7WUFDckMsT0FBTyxFQUFFLE9BQU8sQ0FBQyxPQUFPLElBQUksYUFBYTtZQUN6QyxZQUFZLEVBQUUsT0FBTyxDQUFDLFlBQVksSUFBSSxJQUFJO1lBQzFDLFFBQVEsRUFBRSxPQUFPLENBQUMsUUFBUSxJQUFJLEtBQUs7WUFDbkMsT0FBTyxFQUFFLE9BQU8sQ0FBQyxPQUFPLElBQUksQ0FBQyxHQUFHLEVBQUUsR0FBRyxFQUFFLEdBQUcsRUFBRSxHQUFHLEVBQUUsR0FBRyxFQUFFLEdBQUcsQ0FBQztZQUMxRCxPQUFPLEVBQUUsT0FBTyxDQUFDLE9BQU8sSUFBSSxDQUFDLEdBQUcsRUFBRSxHQUFFLENBQUMsQ0FBQztTQUN2QyxDQUFDO0lBQ0osQ0FBQztJQUVEOztPQUVHO0lBQ0ksS0FBSyxDQUFDLE9BQU8sQ0FDbEIsU0FBMkIsRUFDM0IsYUFBd0Q7UUFFeEQsSUFBSSxTQUFnQixDQUFDO1FBQ3JCLElBQUksWUFBa0MsQ0FBQztRQUV2QyxLQUFLLElBQUksT0FBTyxHQUFHLENBQUMsRUFBRSxPQUFPLElBQUksSUFBSSxDQUFDLE9BQU8sQ0FBQyxXQUFXLEVBQUUsT0FBTyxFQUFFLEVBQUUsQ0FBQztZQUNyRSxJQUFJLENBQUM7Z0JBQ0gsTUFBTSxNQUFNLEdBQUcsTUFBTSxTQUFTLEVBQUUsQ0FBQztn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