The problem
Need to retry failed operations with intelligent backoff strategy
The solution
async function retryWithBackoff(
operation,
options = {}
) {
const {
maxRetries = 3,
initialDelay = 1000,
maxDelay = 30000,
backoffFactor = 2,
jitter = true,
shouldRetry = (error) => true,
onRetry = (error, attempt, delay) => {},
timeout = 0
} = options;
let lastError;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
if (timeout > 0) {
// Add timeout to operation
return await Promise.race([
operation(),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Operation timeout')), timeout)
)
]);
}
return await operation();
} catch (error) {
lastError = error;
if (attempt === maxRetries || !shouldRetry(error)) {
throw error;
}
// Calculate delay with exponential backoff
const baseDelay = initialDelay * Math.pow(backoffFactor, attempt);
// Add jitter (randomness)
let delay = baseDelay;
if (jitter) {
const jitterAmount = baseDelay * 0.1; // 10% jitter
delay += Math.random() * jitterAmount * 2 - jitterAmount;
}
// Cap at max delay
delay = Math.min(delay, maxDelay);
// Notify about retry
onRetry(error, attempt + 1, Math.round(delay));
// Wait before retry
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw lastError;
}
// Circuit breaker pattern
class CircuitBreaker {
constructor(options = {}) {
this.failureThreshold = options.failureThreshold || 5;
this.resetTimeout = options.resetTimeout || 60000;
this.state = 'CLOSED';
this.failureCount = 0;
this.nextAttempt = 0;
this.lastFailureTime = null;
}
async execute(operation) {
if (this.state === 'OPEN') {
if (Date.now() >= this.nextAttempt) {
this.state = 'HALF_OPEN';
} else {
throw new Error('Circuit breaker is OPEN');
}
}
try {
const result = await operation();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
onSuccess() {
this.failureCount = 0;
this.state = 'CLOSED';
this.lastFailureTime = null;
}
onFailure() {
this.failureCount++;
this.lastFailureTime = Date.now();
if (this.failureCount >= this.failureThreshold) {
this.state = 'OPEN';
this.nextAttempt = Date.now() + this.resetTimeout;
// Auto-reset after timeout
setTimeout(() => {
if (this.state === 'OPEN') {
this.state = 'HALF_OPEN';
}
}, this.resetTimeout);
}
}
getState() {
return {
state: this.state,
failureCount: this.failureCount,
lastFailureTime: this.lastFailureTime,
nextAttempt: this.nextAttempt
};
}
}Parameters
operationfunctionAsync function to retry
optionsobjectRetry configuration options
Put it to work
// Simple retry with exponential backoff
async function fetchWithRetry(url) {
return retryWithBackoff(
async () => {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.json();
},
{
maxRetries: 5,
initialDelay: 1000,
backoffFactor: 2,
onRetry: (error, attempt, delay) => {
console.log(`Attempt ${attempt} failed, retrying in ${delay}ms: ${error.message}`);
},
shouldRetry: (error) => {
// Don't retry 404 errors
return !error.message.includes('404');
}
}
);
}
// Circuit breaker example
const breaker = new CircuitBreaker({
failureThreshold: 3,
resetTimeout: 30000
});
async function callProtectedApi() {
return breaker.execute(async () => {
const response = await fetch('https://api.example.com/data');
return response.json();
});
}
// Monitor circuit state
setInterval(() => {
console.log('Circuit state:', breaker.getState());
}, 5000);