TYPESCRIPT

Retry Mechanism with Exponential Backoff

Configurable retry utility for async operations with backoff strategies

TypeScriptAsyncError Handling

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()
  }
);