@push.rocks/webrequest
Version:
Modern, fetch-compatible web request library with intelligent HTTP caching, retry strategies, and fault tolerance.
148 lines • 11.7 kB
JavaScript
/**
* 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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