Retry Mechanism with Exponential Backoff
Configurable retry utility for async operations with backoff strategies
Code
interface RetryOptions<T> {
maxAttempts?: number;
delay?: number;
maxDelay?: number;
factor?: number;
jitter?: boolean;
shouldRetry?: (error: any, attempt: number) => boolean;
onRetry?: (error: any, attempt: number, delay: number) => void;
timeout?: number;
}
type RetryResult<T> = {
success: true;
value: T;
attempts: number;
duration: number;
} | {
success: false;
error: any;
attempts: number;
duration: number;
};
class RetryManager {
static async execute<T>(
fn: () => Promise<T>,
options: RetryOptions<T> = {}
): Promise<RetryResult<T>> {
const {
maxAttempts = 3,
delay = 1000,
maxDelay = 30000,
factor = 2,
jitter = true,
shouldRetry = () => true,
onRetry,
timeout
} = options;
const startTime = Date.now();
let lastError: any;
__TOKEN_43__ (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
let promise = fn();
// Add timeout if specified
__TOKEN_47__ (timeout) {
promise = Promise.race([
promise,
new Promise<never>((_, reject) =>
setTimeout(() => reject(new Error('Operation timeout')), timeout)
)
]);
}
const value = await promise;
const duration = Date.now() - startTime;
return {
success: true,
value,
attempts: attempt,
duration
};
} __TOKEN_54__ (error) {
lastError = error;
// Check if we should retry
__TOKEN_55__ (attempt === maxAttempts || !shouldRetry(error, attempt)) {
const duration = Date.now() - startTime;
return {
success: false,
error,
attempts: attempt,
duration
};
}
// Calculate delay with exponential backoff
const baseDelay = delay * Math.pow(factor, attempt - 1);
const cappedDelay = Math.min(baseDelay, maxDelay);
// Add jitter if enabled
const actualDelay = jitter
? cappedDelay * (0.5 + Math.random() * 0.5)
: cappedDelay;
// Call onRetry callback
onRetry?.(error, attempt, actualDelay);
// Wait before retrying
__TOKEN_61__ (actualDelay > 0) {
await new __TOKEN_152__(resolve => setTimeout(resolve, actualDelay));
}
}
}
const duration = Date.now() - startTime;
return {
success: false,
error: lastError,
attempts: maxAttempts,
duration
};
}
// Factory method for creating retryable functions
static create<T>(
fn: () => Promise<T>,
options: RetryOptions<T> = {}
): () => Promise<T> {
return __TOKEN_68__ () => {
const result = await RetryManager.execute(fn, options);
__TOKEN_71__ (!result.success) {
throw result.error;
}
return result.value;
};
}
// Circuit breaker pattern
static withCircuitBreaker<T>(
fn: () => Promise<T>,
options: {
failureThreshold: number;
resetTimeout: number;
retryOptions?: RetryOptions<T>;
}
): () => Promise<T> {
let state: 'CLOSED' | 'OPEN' | 'HALF_OPEN' = 'CLOSED';
let failureCount = 0;
let lastFailureTime = 0;
return __TOKEN_79__ () => {
const now = Date.now();
// Check if circuit breaker is open
__TOKEN_81__ (state === 'OPEN') {
__TOKEN_82__ (now - lastFailureTime >= options.resetTimeout) {
state = 'HALF_OPEN';
} else {
throw new Error('Circuit breaker is open');
}
}
try {
const result = await RetryManager.execute(fn, options.retryOptions);
__TOKEN_89__ (result.success) {
// Reset on success
__TOKEN_90__ (state === 'HALF_OPEN') {
state = 'CLOSED';
failureCount = 0;
}
return result.value;
} else {
throw result.error;
}
} __TOKEN_94__ (error) {
failureCount++;
lastFailureTime = now;
__TOKEN_95__ (state === 'HALF_OPEN' || failureCount >= options.failureThreshold) {
state = 'OPEN';
}
throw error;
}
};
}
}
// Utility functions for common retry strategies
const retryStrategies = {
exponential: (options?: Partial<RetryOptions<any>>) => ({
maxAttempts: 5,
delay: 1000,
factor: 2,
jitter: true,
...options
}),
fixed: (options?: Partial<RetryOptions<any>>) => ({
maxAttempts: 3,
delay: 2000,
factor: 1,
jitter: false,
...options
}),
immediate: (options?: Partial<RetryOptions<any>>) => ({
maxAttempts: 3,
delay: 0,
...options
}),
network: (options?: Partial<RetryOptions<any>>) => ({
maxAttempts: 3,
delay: 1000,
factor: 2,
maxDelay: 10000,
shouldRetry: (error: any) =>
error.code === 'ETIMEDOUT' ||
error.code === 'ECONNREFUSED' ||
error.message?.includes('network'),
...options
})
};
// Usage examples
async function fetchWithRetry(url: string) {
return RetryManager.execute(
__TOKEN_101__ () => {
const response = await fetch(url);
__TOKEN_104__ (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
},
retryStrategies.network({
maxAttempts: 5,
onRetry: (error, attempt, delay) => {
console.log(`Retry ${attempt} after ${delay}ms: ${error.message}`);
}
})
);
}
// Create retryable API client
const apiClient = {
get: RetryManager.create(
__TOKEN_109__ (endpoint: string) => {
const response = await fetch(`/api/${endpoint}`);
return response.json();
},
retryStrategies.exponential()
),
post: RetryManager.create(
__TOKEN_113__ (endpoint: string, data: any) => {
const response = await fetch(`/api/${endpoint}`, {
method: 'POST',
body: JSON.stringify(data)
});
return response.json();
},
retryStrategies.fixed()
)
};
// With circuit breaker
const resilientApiCall = RetryManager.withCircuitBreaker(
__TOKEN_118__ () => {
const response = await fetch('/api/sensitive');
return response.json();
},
{
failureThreshold: 5,
resetTimeout: 60000,
retryOptions: retryStrategies.exponential()
}
);