+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 103 of 355

🚨 Error Handling in Async Code: Try/Catch Patterns Mastery

Master robust error handling in TypeScript async code with advanced try/catch patterns, error boundaries, and bulletproof recovery strategies πŸ›‘οΈ

πŸš€Intermediate
23 min read

Prerequisites

  • Basic TypeScript syntax and types πŸ“
  • Understanding of Promises and async/await ⚑
  • Promise chaining and parallel operations πŸ”—

What you'll learn

  • Master try/catch patterns for bulletproof async error handling πŸ›‘οΈ
  • Implement sophisticated error recovery and fallback strategies πŸ”„
  • Design resilient async flows with custom error types and boundaries 🚧
  • Build production-ready error monitoring and logging systems πŸ“Š

🎯 Introduction

Welcome to the critical world of async error handling in TypeScript! πŸŽ‰ In this guide, we’ll explore how to write bulletproof async code that gracefully handles errors and recovers from failures like a professional system.

You’ll discover how to transform fragile async operations into resilient, self-healing systems that provide excellent user experiences even when things go wrong. Whether you’re building APIs 🌐, processing data streams πŸ“Š, or orchestrating complex workflows 🏭, mastering async error handling is essential for creating production-ready TypeScript applications.

By the end of this tutorial, you’ll be writing async code that’s not just functional, but unbreakable and ready for the real world! 🚨 Let’s dive in! πŸŠβ€β™‚οΈ

πŸ“š Understanding Async Error Handling

πŸ€” What Makes Async Errors Different?

Async errors are like hidden landmines in your code πŸ’£. Unlike synchronous errors that happen immediately and are easy to track, async errors can occur at any time, in any order, and can cascade through your application in unexpected ways.

In TypeScript async contexts, errors present unique challenges:

  • ✨ Timing unpredictability - errors can happen at any point in the async flow
  • πŸš€ Cascade effects - one failed operation can break entire workflows
  • πŸ›‘οΈ Context loss - error stack traces can be harder to follow
  • πŸ“¦ Multiple failure modes - network, validation, business logic, and system errors

πŸ’‘ Why Master Async Error Handling?

Here’s why robust async error handling is critical:

  1. User Experience 😊: Graceful degradation instead of crashes
  2. System Reliability πŸ”§: Services that recover from failures automatically
  3. Debugging Efficiency πŸ”: Clear error information for quick fixes
  4. Business Continuity πŸ’Ό: Operations continue even when components fail
  5. Monitoring & Alerting πŸ“Š: Proactive issue detection and resolution

Real-world example: When a payment service goes down, your e-commerce site should gracefully fall back to alternative payment methods rather than showing a blank error page! πŸ’³

πŸ”§ Basic Try/Catch Patterns

πŸ“ Essential Try/Catch Structure

Let’s start with fundamental async error handling patterns:

// 🎯 Basic try/catch with async/await
interface ApiResponse<T> {
  success: boolean;
  data?: T;
  error?: string;
  timestamp: Date;
}

// βœ… Proper async error handling structure
const fetchUserData = async (userId: string): Promise<ApiResponse<User>> => {
  try {
    console.log('πŸ‘€ Fetching user data...');
    
    // πŸ” Input validation first
    if (!userId || userId.trim() === '') {
      throw new Error('User ID is required');
    }
    
    // 🌐 API call that might fail
    const userData = await callUserAPI(userId);
    
    console.log('βœ… User data fetched successfully');
    return {
      success: true,
      data: userData,
      timestamp: new Date()
    };
    
  } catch (error) {
    // 🚨 Error handling with proper logging
    console.error('πŸ’₯ Failed to fetch user data:', error.message);
    
    return {
      success: false,
      error: error.message || 'Unknown error occurred',
      timestamp: new Date()
    };
  }
};

// πŸ—οΈ Type definitions for clarity
interface User {
  id: string;
  name: string;
  email: string;
  status: 'active' | 'inactive';
}

// πŸ› οΈ Mock API that sometimes fails
const callUserAPI = async (userId: string): Promise<User> => {
  await new Promise(resolve => setTimeout(resolve, 200));
  
  // 🎲 Simulate different types of failures
  const randomFailure = Math.random();
  
  if (randomFailure < 0.1) {
    throw new Error('Network timeout - please try again');
  }
  
  if (randomFailure < 0.2) {
    throw new Error('User service temporarily unavailable');
  }
  
  if (userId === 'invalid') {
    throw new Error('User not found');
  }
  
  return {
    id: userId,
    name: 'John Doe',
    email: '[email protected]',
    status: 'active'
  };
};

πŸ”„ Multiple Try/Catch Blocks for Granular Control

// 🎯 Granular error handling for complex workflows
interface ProcessingResult {
  step: string;
  success: boolean;
  data?: any;
  error?: string;
  duration: number;
}

interface WorkflowResult {
  overallSuccess: boolean;
  results: ProcessingResult[];
  totalDuration: number;
  failedSteps: string[];
}

// 🏭 Complex workflow with step-by-step error handling
const processUserWorkflow = async (userId: string): Promise<WorkflowResult> => {
  const results: ProcessingResult[] = [];
  const failedSteps: string[] = [];
  const workflowStart = Date.now();
  
  // πŸ“ Step 1: Validate user input
  let stepStart = Date.now();
  try {
    console.log('πŸ” Step 1: Validating user input...');
    
    if (!userId || userId.length < 3) {
      throw new Error('Invalid user ID format');
    }
    
    results.push({
      step: 'validation',
      success: true,
      data: { userId },
      duration: Date.now() - stepStart
    });
    
    console.log('βœ… Step 1 completed: Input validation successful');
    
  } catch (error) {
    console.error('❌ Step 1 failed:', error.message);
    
    results.push({
      step: 'validation',
      success: false,
      error: error.message,
      duration: Date.now() - stepStart
    });
    
    failedSteps.push('validation');
    
    // πŸ›‘ Critical step failed - abort workflow
    return {
      overallSuccess: false,
      results,
      totalDuration: Date.now() - workflowStart,
      failedSteps
    };
  }
  
  // πŸ‘€ Step 2: Fetch user data (optional - continue on failure)
  stepStart = Date.now();
  let userData: User | null = null;
  
  try {
    console.log('πŸ‘€ Step 2: Fetching user data...');
    
    userData = await callUserAPI(userId);
    
    results.push({
      step: 'user_fetch',
      success: true,
      data: userData,
      duration: Date.now() - stepStart
    });
    
    console.log('βœ… Step 2 completed: User data fetched');
    
  } catch (error) {
    console.warn('⚠️ Step 2 failed (non-critical):', error.message);
    
    results.push({
      step: 'user_fetch',
      success: false,
      error: error.message,
      duration: Date.now() - stepStart
    });
    
    failedSteps.push('user_fetch');
    // Continue workflow - this step is optional
  }
  
  // πŸ“Š Step 3: Process user analytics
  stepStart = Date.now();
  
  try {
    console.log('πŸ“Š Step 3: Processing user analytics...');
    
    const analyticsData = await processUserAnalytics(userId, userData);
    
    results.push({
      step: 'analytics',
      success: true,
      data: analyticsData,
      duration: Date.now() - stepStart
    });
    
    console.log('βœ… Step 3 completed: Analytics processed');
    
  } catch (error) {
    console.warn('⚠️ Step 3 failed (non-critical):', error.message);
    
    results.push({
      step: 'analytics',
      success: false,
      error: error.message,
      duration: Date.now() - stepStart
    });
    
    failedSteps.push('analytics');
    // Continue workflow - analytics is optional
  }
  
  // πŸ“§ Step 4: Send notifications
  stepStart = Date.now();
  
  try {
    console.log('πŸ“§ Step 4: Sending notifications...');
    
    const notificationResult = await sendUserNotifications(userId);
    
    results.push({
      step: 'notifications',
      success: true,
      data: notificationResult,
      duration: Date.now() - stepStart
    });
    
    console.log('βœ… Step 4 completed: Notifications sent');
    
  } catch (error) {
    console.warn('⚠️ Step 4 failed (non-critical):', error.message);
    
    results.push({
      step: 'notifications',
      success: false,
      error: error.message,
      duration: Date.now() - stepStart
    });
    
    failedSteps.push('notifications');
    // Continue - notifications are optional
  }
  
  const totalDuration = Date.now() - workflowStart;
  const overallSuccess = failedSteps.length === 0;
  
  console.log(`🏁 Workflow completed in ${totalDuration}ms. Success: ${overallSuccess}`);
  
  return {
    overallSuccess,
    results,
    totalDuration,
    failedSteps
  };
};

// πŸ› οΈ Mock workflow functions
const processUserAnalytics = async (userId: string, userData: User | null) => {
  await new Promise(resolve => setTimeout(resolve, 150));
  
  if (Math.random() < 0.15) { // 15% failure rate
    throw new Error('Analytics service temporarily unavailable');
  }
  
  return {
    userId,
    sessionCount: Math.floor(Math.random() * 100),
    lastActivity: new Date(),
    hasUserData: userData !== null
  };
};

const sendUserNotifications = async (userId: string) => {
  await new Promise(resolve => setTimeout(resolve, 100));
  
  if (Math.random() < 0.1) { // 10% failure rate
    throw new Error('Notification service temporarily unavailable');
  }
  
  return {
    emailSent: true,
    pushSent: true,
    smsRequired: false
  };
};

🚨 Custom Error Types and Classification

🏷️ Structured Error Hierarchy

// 🎯 Custom error classes for better error categorization
abstract class BaseError extends Error {
  abstract readonly code: string;
  abstract readonly statusCode: number;
  abstract readonly isRetryable: boolean;
  
  public readonly timestamp: Date;
  public readonly context?: Record<string, any>;
  
  constructor(message: string, context?: Record<string, any>) {
    super(message);
    this.name = this.constructor.name;
    this.timestamp = new Date();
    this.context = context;
  }
}

// 🌐 Network-related errors
class NetworkError extends BaseError {
  readonly code = 'NETWORK_ERROR';
  readonly statusCode = 503;
  readonly isRetryable = true;
  
  constructor(message: string, context?: Record<string, any>) {
    super(`Network error: ${message}`, context);
  }
}

// πŸ” Validation errors
class ValidationError extends BaseError {
  readonly code = 'VALIDATION_ERROR';
  readonly statusCode = 400;
  readonly isRetryable = false;
  
  public readonly errors: Array<{
    field: string;
    message: string;
    value?: any;
  }>;
  
  constructor(errors: Array<{ field: string; message: string; value?: any }>) {
    const message = `Validation failed: ${errors.map(e => e.message).join(', ')}`;
    super(message);
    this.errors = errors;
  }
}

// 🚫 Authorization errors
class AuthorizationError extends BaseError {
  readonly code = 'AUTHORIZATION_ERROR';
  readonly statusCode = 403;
  readonly isRetryable = false;
  
  constructor(message: string = 'Access denied') {
    super(message);
  }
}

// πŸ” Not found errors
class NotFoundError extends BaseError {
  readonly code = 'NOT_FOUND_ERROR';
  readonly statusCode = 404;
  readonly isRetryable = false;
  
  constructor(resource: string, id: string) {
    super(`${resource} with id '${id}' not found`);
  }
}

// βš™οΈ Business logic errors
class BusinessLogicError extends BaseError {
  readonly code = 'BUSINESS_LOGIC_ERROR';
  readonly statusCode = 422;
  readonly isRetryable = false;
  
  constructor(message: string, context?: Record<string, any>) {
    super(`Business rule violation: ${message}`, context);
  }
}

// πŸ”§ System errors
class SystemError extends BaseError {
  readonly code = 'SYSTEM_ERROR';
  readonly statusCode = 500;
  readonly isRetryable = true;
  
  constructor(message: string, context?: Record<string, any>) {
    super(`System error: ${message}`, context);
  }
}

// 🎯 Smart error classification utility
class ErrorClassifier {
  static classify(error: any): BaseError {
    // πŸ” If already a custom error, return as-is
    if (error instanceof BaseError) {
      return error;
    }
    
    const message = error.message || 'Unknown error';
    const lowerMessage = message.toLowerCase();
    
    // 🌐 Network-related patterns
    if (lowerMessage.includes('network') || 
        lowerMessage.includes('timeout') || 
        lowerMessage.includes('connection') ||
        lowerMessage.includes('fetch')) {
      return new NetworkError(message);
    }
    
    // πŸ” Validation patterns
    if (lowerMessage.includes('validation') || 
        lowerMessage.includes('invalid') || 
        lowerMessage.includes('required')) {
      return new ValidationError([{
        field: 'unknown',
        message: message
      }]);
    }
    
    // 🚫 Authorization patterns
    if (lowerMessage.includes('unauthorized') || 
        lowerMessage.includes('forbidden') || 
        lowerMessage.includes('access denied')) {
      return new AuthorizationError(message);
    }
    
    // πŸ” Not found patterns
    if (lowerMessage.includes('not found') || 
        lowerMessage.includes('does not exist')) {
      return new NotFoundError('Resource', 'unknown');
    }
    
    // πŸ”§ Default to system error
    return new SystemError(message);
  }
}

// πŸ›‘οΈ Enhanced error handling with classification
const enhancedAsyncOperation = async (userId: string): Promise<ApiResponse<User>> => {
  try {
    console.log('πŸš€ Starting enhanced async operation...');
    
    // βœ… Input validation with custom errors
    if (!userId) {
      throw new ValidationError([{
        field: 'userId',
        message: 'User ID is required',
        value: userId
      }]);
    }
    
    if (userId.length < 3) {
      throw new ValidationError([{
        field: 'userId',
        message: 'User ID must be at least 3 characters',
        value: userId
      }]);
    }
    
    // πŸ” Authorization check
    const hasPermission = await checkUserPermission(userId);
    if (!hasPermission) {
      throw new AuthorizationError('User does not have permission to access this resource');
    }
    
    // πŸ‘€ Fetch user data
    const userData = await fetchUserWithRetry(userId);
    
    console.log('βœ… Enhanced operation completed successfully');
    
    return {
      success: true,
      data: userData,
      timestamp: new Date()
    };
    
  } catch (error) {
    // 🎯 Classify and handle error appropriately
    const classifiedError = ErrorClassifier.classify(error);
    
    console.error(`πŸ’₯ ${classifiedError.code}: ${classifiedError.message}`);
    
    // πŸ“Š Log error details for monitoring
    logError(classifiedError, { operation: 'enhancedAsyncOperation', userId });
    
    return {
      success: false,
      error: classifiedError.message,
      timestamp: new Date()
    };
  }
};

// πŸ› οΈ Helper functions
const checkUserPermission = async (userId: string): Promise<boolean> => {
  await new Promise(resolve => setTimeout(resolve, 50));
  
  // 🎲 Simulate permission check
  if (userId === 'unauthorized') {
    return false;
  }
  
  return true;
};

const fetchUserWithRetry = async (userId: string): Promise<User> => {
  if (userId === 'notfound') {
    throw new NotFoundError('User', userId);
  }
  
  // 🎲 Simulate network issues
  if (Math.random() < 0.2) {
    throw new NetworkError('Unable to connect to user service');
  }
  
  await new Promise(resolve => setTimeout(resolve, 100));
  
  return {
    id: userId,
    name: 'Jane Smith',
    email: '[email protected]',
    status: 'active'
  };
};

const logError = (error: BaseError, context: Record<string, any>) => {
  const logEntry = {
    timestamp: error.timestamp,
    code: error.code,
    message: error.message,
    statusCode: error.statusCode,
    isRetryable: error.isRetryable,
    context: { ...error.context, ...context }
  };
  
  console.error('πŸ“‹ Error logged:', JSON.stringify(logEntry, null, 2));
};

πŸ”„ Retry Mechanisms and Exponential Backoff

⚑ Intelligent Retry Strategies

// 🎯 Advanced retry mechanism with exponential backoff
interface RetryConfig {
  maxAttempts: number;
  baseDelayMs: number;
  maxDelayMs: number;
  backoffMultiplier: number;
  jitterMs: number;
  retryableErrorCodes: string[];
}

interface RetryResult<T> {
  success: boolean;
  data?: T;
  error?: BaseError;
  attempts: number;
  totalDuration: number;
  attemptDetails: Array<{
    attempt: number;
    success: boolean;
    duration: number;
    error?: string;
  }>;
}

// πŸ”„ Smart retry utility with comprehensive tracking
class RetryManager {
  private static defaultConfig: RetryConfig = {
    maxAttempts: 3,
    baseDelayMs: 1000,
    maxDelayMs: 30000,
    backoffMultiplier: 2,
    jitterMs: 100,
    retryableErrorCodes: ['NETWORK_ERROR', 'SYSTEM_ERROR']
  };
  
  static async execute<T>(
    operation: () => Promise<T>,
    config: Partial<RetryConfig> = {}
  ): Promise<RetryResult<T>> {
    const finalConfig = { ...this.defaultConfig, ...config };
    const attemptDetails: RetryResult<T>['attemptDetails'] = [];
    const startTime = Date.now();
    
    let lastError: BaseError;
    
    for (let attempt = 1; attempt <= finalConfig.maxAttempts; attempt++) {
      const attemptStart = Date.now();
      
      try {
        console.log(`πŸ”„ Retry attempt ${attempt}/${finalConfig.maxAttempts}`);
        
        // πŸš€ Execute the operation
        const result = await operation();
        
        const attemptDuration = Date.now() - attemptStart;
        attemptDetails.push({
          attempt,
          success: true,
          duration: attemptDuration
        });
        
        console.log(`βœ… Operation succeeded on attempt ${attempt}`);
        
        return {
          success: true,
          data: result,
          attempts: attempt,
          totalDuration: Date.now() - startTime,
          attemptDetails
        };
        
      } catch (error) {
        const classifiedError = ErrorClassifier.classify(error);
        lastError = classifiedError;
        
        const attemptDuration = Date.now() - attemptStart;
        attemptDetails.push({
          attempt,
          success: false,
          duration: attemptDuration,
          error: classifiedError.message
        });
        
        console.warn(`❌ Attempt ${attempt} failed: ${classifiedError.message}`);
        
        // πŸ” Check if error is retryable
        if (!finalConfig.retryableErrorCodes.includes(classifiedError.code)) {
          console.error(`πŸ›‘ Error is not retryable: ${classifiedError.code}`);
          break;
        }
        
        // πŸ”š Don't retry on last attempt
        if (attempt === finalConfig.maxAttempts) {
          console.error(`πŸ›‘ Max attempts reached: ${finalConfig.maxAttempts}`);
          break;
        }
        
        // ⏱️ Calculate delay with exponential backoff and jitter
        const baseDelay = finalConfig.baseDelayMs * Math.pow(finalConfig.backoffMultiplier, attempt - 1);
        const jitter = Math.random() * finalConfig.jitterMs;
        const delay = Math.min(baseDelay + jitter, finalConfig.maxDelayMs);
        
        console.log(`⏳ Waiting ${Math.round(delay)}ms before retry...`);
        await new Promise(resolve => setTimeout(resolve, delay));
      }
    }
    
    return {
      success: false,
      error: lastError!,
      attempts: finalConfig.maxAttempts,
      totalDuration: Date.now() - startTime,
      attemptDetails
    };
  }
}

// 🎯 Retry-enabled API operations
const fetchUserWithIntelligentRetry = async (userId: string): Promise<User> => {
  const retryResult = await RetryManager.execute(
    () => callUserAPI(userId),
    {
      maxAttempts: 5,
      baseDelayMs: 500,
      maxDelayMs: 10000,
      backoffMultiplier: 1.5,
      jitterMs: 200
    }
  );
  
  if (retryResult.success) {
    console.log(`πŸŽ‰ User fetched successfully after ${retryResult.attempts} attempts`);
    return retryResult.data!;
  } else {
    console.error(`πŸ’₯ Failed to fetch user after ${retryResult.attempts} attempts`);
    throw retryResult.error!;
  }
};

// πŸš€ Parallel operations with retry
const fetchMultipleUsersWithRetry = async (userIds: string[]): Promise<{
  successful: User[];
  failed: Array<{ userId: string; error: BaseError }>;
}> => {
  console.log(`πŸ‘₯ Fetching ${userIds.length} users with retry...`);
  
  const operations = userIds.map(async (userId) => {
    try {
      const user = await fetchUserWithIntelligentRetry(userId);
      return { success: true, userId, user };
    } catch (error) {
      return { success: false, userId, error: ErrorClassifier.classify(error) };
    }
  });
  
  const results = await Promise.allSettled(operations);
  
  const successful: User[] = [];
  const failed: Array<{ userId: string; error: BaseError }> = [];
  
  results.forEach((result, index) => {
    if (result.status === 'fulfilled') {
      if (result.value.success) {
        successful.push(result.value.user);
      } else {
        failed.push({
          userId: result.value.userId,
          error: result.value.error
        });
      }
    } else {
      failed.push({
        userId: userIds[index],
        error: new SystemError('Promise rejected unexpectedly')
      });
    }
  });
  
  console.log(`βœ… Successful: ${successful.length}, Failed: ${failed.length}`);
  
  return { successful, failed };
};

πŸ›‘οΈ Error Boundaries and Circuit Breakers

πŸ”§ Advanced Resilience Patterns

// 🎯 Circuit breaker pattern for preventing cascade failures
enum CircuitState {
  CLOSED = 'CLOSED',     // Normal operation
  OPEN = 'OPEN',         // Blocking requests
  HALF_OPEN = 'HALF_OPEN' // Testing if service recovered
}

interface CircuitBreakerConfig {
  failureThreshold: number;    // Number of failures to open circuit
  recoveryTimeMs: number;      // Time to wait before half-open
  successThreshold: number;    // Successes needed to close circuit
  timeWindowMs: number;        // Time window for failure counting
}

interface CircuitBreakerMetrics {
  state: CircuitState;
  failures: number;
  successes: number;
  lastFailureTime?: Date;
  lastSuccessTime?: Date;
  totalRequests: number;
  blockedRequests: number;
}

// πŸ”Œ Circuit breaker implementation
class CircuitBreaker {
  private state = CircuitState.CLOSED;
  private failures = 0;
  private successes = 0;
  private lastFailureTime?: Date;
  private lastSuccessTime?: Date;
  private totalRequests = 0;
  private blockedRequests = 0;
  private recentFailures: Date[] = [];
  
  constructor(
    private config: CircuitBreakerConfig,
    private name: string = 'default'
  ) {}
  
  async execute<T>(operation: () => Promise<T>): Promise<T> {
    this.totalRequests++;
    
    // πŸ” Check circuit state
    if (this.state === CircuitState.OPEN) {
      if (this.shouldAttemptRecovery()) {
        console.log(`πŸ”„ Circuit breaker '${this.name}' moving to HALF_OPEN`);
        this.state = CircuitState.HALF_OPEN;
      } else {
        this.blockedRequests++;
        throw new SystemError(`Circuit breaker '${this.name}' is OPEN - service unavailable`);
      }
    }
    
    try {
      // πŸš€ Execute operation
      const result = await operation();
      
      // βœ… Operation succeeded
      this.onSuccess();
      
      return result;
      
    } catch (error) {
      // ❌ Operation failed
      this.onFailure();
      throw error;
    }
  }
  
  private onSuccess(): void {
    this.successes++;
    this.lastSuccessTime = new Date();
    
    if (this.state === CircuitState.HALF_OPEN) {
      if (this.successes >= this.config.successThreshold) {
        console.log(`βœ… Circuit breaker '${this.name}' closing after ${this.successes} successes`);
        this.state = CircuitState.CLOSED;
        this.reset();
      }
    } else if (this.state === CircuitState.CLOSED) {
      // 🧹 Clean old failures in closed state
      this.cleanOldFailures();
    }
  }
  
  private onFailure(): void {
    this.failures++;
    this.lastFailureTime = new Date();
    this.recentFailures.push(new Date());
    
    // 🧹 Clean old failures
    this.cleanOldFailures();
    
    if (this.state === CircuitState.HALF_OPEN) {
      console.log(`❌ Circuit breaker '${this.name}' opening due to failure in HALF_OPEN`);
      this.state = CircuitState.OPEN;
    } else if (this.state === CircuitState.CLOSED) {
      if (this.recentFailures.length >= this.config.failureThreshold) {
        console.log(`πŸ”₯ Circuit breaker '${this.name}' opening due to ${this.recentFailures.length} recent failures`);
        this.state = CircuitState.OPEN;
      }
    }
  }
  
  private shouldAttemptRecovery(): boolean {
    if (!this.lastFailureTime) return false;
    
    const timeSinceLastFailure = Date.now() - this.lastFailureTime.getTime();
    return timeSinceLastFailure >= this.config.recoveryTimeMs;
  }
  
  private cleanOldFailures(): void {
    const cutoffTime = Date.now() - this.config.timeWindowMs;
    this.recentFailures = this.recentFailures.filter(
      failureTime => failureTime.getTime() > cutoffTime
    );
  }
  
  private reset(): void {
    this.failures = 0;
    this.successes = 0;
    this.recentFailures = [];
  }
  
  getMetrics(): CircuitBreakerMetrics {
    return {
      state: this.state,
      failures: this.failures,
      successes: this.successes,
      lastFailureTime: this.lastFailureTime,
      lastSuccessTime: this.lastSuccessTime,
      totalRequests: this.totalRequests,
      blockedRequests: this.blockedRequests
    };
  }
}

// 🏭 Service wrapper with circuit breaker
class ResilientUserService {
  private circuitBreaker: CircuitBreaker;
  
  constructor() {
    this.circuitBreaker = new CircuitBreaker({
      failureThreshold: 5,        // Open after 5 failures
      recoveryTimeMs: 30000,      // Wait 30 seconds before retry
      successThreshold: 3,        // Close after 3 successes
      timeWindowMs: 60000         // 1-minute failure window
    }, 'user-service');
  }
  
  async getUser(userId: string): Promise<User> {
    return this.circuitBreaker.execute(async () => {
      console.log(`πŸ‘€ Fetching user ${userId} through circuit breaker...`);
      return await callUserAPI(userId);
    });
  }
  
  async getUsers(userIds: string[]): Promise<{
    users: User[];
    errors: Array<{ userId: string; error: string }>;
  }> {
    const users: User[] = [];
    const errors: Array<{ userId: string; error: string }> = [];
    
    // πŸš€ Process in batches to avoid overwhelming the circuit breaker
    const batchSize = 3;
    for (let i = 0; i < userIds.length; i += batchSize) {
      const batch = userIds.slice(i, i + batchSize);
      
      const batchPromises = batch.map(async (userId) => {
        try {
          const user = await this.getUser(userId);
          users.push(user);
        } catch (error) {
          errors.push({
            userId,
            error: error.message
          });
        }
      });
      
      await Promise.allSettled(batchPromises);
      
      // πŸ’€ Small delay between batches
      if (i + batchSize < userIds.length) {
        await new Promise(resolve => setTimeout(resolve, 100));
      }
    }
    
    return { users, errors };
  }
  
  getServiceMetrics(): CircuitBreakerMetrics {
    return this.circuitBreaker.getMetrics();
  }
}

🎯 Real-World Application: Payment Processing System

πŸ’³ Comprehensive Payment Error Handling

// 🎯 Payment processing with robust error handling
interface PaymentRequest {
  orderId: string;
  amount: number;
  currency: string;
  paymentMethodId: string;
  customerId: string;
}

interface PaymentResult {
  success: boolean;
  transactionId?: string;
  status: 'pending' | 'completed' | 'failed' | 'cancelled';
  amount?: number;
  processingFee?: number;
  error?: string;
  retryable: boolean;
  processingTime: number;
}

// 🚨 Payment-specific errors
class PaymentError extends BaseError {
  readonly code = 'PAYMENT_ERROR';
  readonly statusCode = 402;
  readonly isRetryable: boolean;
  
  constructor(message: string, isRetryable: boolean = false, context?: Record<string, any>) {
    super(`Payment failed: ${message}`, context);
    this.isRetryable = isRetryable;
  }
}

class InsufficientFundsError extends PaymentError {
  constructor() {
    super('Insufficient funds', false);
  }
}

class PaymentMethodError extends PaymentError {
  constructor(message: string) {
    super(`Payment method error: ${message}`, false);
  }
}

class PaymentGatewayError extends PaymentError {
  constructor(message: string) {
    super(`Gateway error: ${message}`, true);
  }
}

// πŸ’³ Resilient payment processor
class PaymentProcessor {
  private primaryGateway: CircuitBreaker;
  private fallbackGateway: CircuitBreaker;
  
  constructor() {
    this.primaryGateway = new CircuitBreaker({
      failureThreshold: 3,
      recoveryTimeMs: 60000,      // 1 minute
      successThreshold: 2,
      timeWindowMs: 300000        // 5 minutes
    }, 'primary-payment-gateway');
    
    this.fallbackGateway = new CircuitBreaker({
      failureThreshold: 5,
      recoveryTimeMs: 30000,      // 30 seconds
      successThreshold: 3,
      timeWindowMs: 180000        // 3 minutes
    }, 'fallback-payment-gateway');
  }
  
  async processPayment(request: PaymentRequest): Promise<PaymentResult> {
    const startTime = Date.now();
    
    try {
      console.log(`πŸ’³ Processing payment for order ${request.orderId}...`);
      
      // βœ… Step 1: Validate payment request
      await this.validatePaymentRequest(request);
      
      // πŸ’³ Step 2: Try primary gateway
      try {
        const result = await this.primaryGateway.execute(() => 
          this.callPrimaryGateway(request)
        );
        
        console.log(`βœ… Payment processed via primary gateway: ${result.transactionId}`);
        
        return {
          ...result,
          processingTime: Date.now() - startTime
        };
        
      } catch (primaryError) {
        console.warn('⚠️ Primary gateway failed, trying fallback...');
        
        // πŸ”„ Step 3: Try fallback gateway
        try {
          const result = await this.fallbackGateway.execute(() => 
            this.callFallbackGateway(request)
          );
          
          console.log(`βœ… Payment processed via fallback gateway: ${result.transactionId}`);
          
          return {
            ...result,
            processingTime: Date.now() - startTime
          };
          
        } catch (fallbackError) {
          console.error('πŸ’₯ Both gateways failed');
          
          // 🚨 Both gateways failed - determine if retryable
          const classifiedError = ErrorClassifier.classify(fallbackError);
          const retryable = classifiedError.isRetryable;
          
          return {
            success: false,
            status: 'failed',
            error: `Payment processing failed: ${classifiedError.message}`,
            retryable,
            processingTime: Date.now() - startTime
          };
        }
      }
      
    } catch (error) {
      // ❌ Validation or other critical error
      const classifiedError = ErrorClassifier.classify(error);
      
      console.error(`πŸ’₯ Payment processing failed: ${classifiedError.message}`);
      
      return {
        success: false,
        status: 'failed',
        error: classifiedError.message,
        retryable: classifiedError.isRetryable,
        processingTime: Date.now() - startTime
      };
    }
  }
  
  // πŸ”„ Batch payment processing with error isolation
  async processPaymentBatch(requests: PaymentRequest[]): Promise<{
    successful: PaymentResult[];
    failed: PaymentResult[];
    summary: {
      totalAmount: number;
      successfulAmount: number;
      failedAmount: number;
      successRate: number;
    };
  }> {
    console.log(`πŸ“¦ Processing batch of ${requests.length} payments...`);
    
    const successful: PaymentResult[] = [];
    const failed: PaymentResult[] = [];
    
    // πŸš€ Process payments in parallel with error isolation
    const results = await Promise.allSettled(
      requests.map(request => this.processPayment(request))
    );
    
    results.forEach((result, index) => {
      if (result.status === 'fulfilled') {
        if (result.value.success) {
          successful.push(result.value);
        } else {
          failed.push(result.value);
        }
      } else {
        // Promise was rejected (shouldn't happen with our error handling)
        failed.push({
          success: false,
          status: 'failed',
          error: 'Unexpected promise rejection',
          retryable: false,
          processingTime: 0
        });
      }
    });
    
    // πŸ“Š Calculate summary
    const totalAmount = requests.reduce((sum, req) => sum + req.amount, 0);
    const successfulAmount = successful.reduce((sum, result) => sum + (result.amount || 0), 0);
    const failedAmount = totalAmount - successfulAmount;
    const successRate = (successful.length / requests.length) * 100;
    
    console.log(`πŸ“Š Batch complete: ${successful.length}/${requests.length} successful (${successRate.toFixed(1)}%)`);
    
    return {
      successful,
      failed,
      summary: {
        totalAmount,
        successfulAmount,
        failedAmount,
        successRate
      }
    };
  }
  
  private async validatePaymentRequest(request: PaymentRequest): Promise<void> {
    const errors: Array<{ field: string; message: string }> = [];
    
    if (!request.orderId) {
      errors.push({ field: 'orderId', message: 'Order ID is required' });
    }
    
    if (!request.amount || request.amount <= 0) {
      errors.push({ field: 'amount', message: 'Amount must be greater than 0' });
    }
    
    if (request.amount > 100000) { // $100k limit
      errors.push({ field: 'amount', message: 'Amount exceeds maximum limit' });
    }
    
    if (!request.currency || request.currency.length !== 3) {
      errors.push({ field: 'currency', message: 'Invalid currency code' });
    }
    
    if (!request.paymentMethodId) {
      errors.push({ field: 'paymentMethodId', message: 'Payment method is required' });
    }
    
    if (!request.customerId) {
      errors.push({ field: 'customerId', message: 'Customer ID is required' });
    }
    
    if (errors.length > 0) {
      throw new ValidationError(errors);
    }
  }
  
  private async callPrimaryGateway(request: PaymentRequest): Promise<PaymentResult> {
    await new Promise(resolve => setTimeout(resolve, 200));
    
    // 🎲 Simulate various failure scenarios
    const random = Math.random();
    
    if (random < 0.1) {
      throw new PaymentGatewayError('Gateway timeout');
    }
    
    if (random < 0.15) {
      throw new InsufficientFundsError();
    }
    
    if (random < 0.2) {
      throw new PaymentMethodError('Card expired');
    }
    
    return {
      success: true,
      transactionId: `txn_primary_${Date.now()}`,
      status: 'completed',
      amount: request.amount,
      processingFee: request.amount * 0.029, // 2.9% fee
      retryable: false,
      processingTime: 0 // Will be set by caller
    };
  }
  
  private async callFallbackGateway(request: PaymentRequest): Promise<PaymentResult> {
    await new Promise(resolve => setTimeout(resolve, 300));
    
    // 🎲 Different failure patterns for fallback
    const random = Math.random();
    
    if (random < 0.05) {
      throw new PaymentGatewayError('Fallback gateway overloaded');
    }
    
    if (random < 0.1) {
      throw new PaymentMethodError('Payment method not supported');
    }
    
    return {
      success: true,
      transactionId: `txn_fallback_${Date.now()}`,
      status: 'completed',
      amount: request.amount,
      processingFee: request.amount * 0.035, // 3.5% fee (higher for fallback)
      retryable: false,
      processingTime: 0 // Will be set by caller
    };
  }
  
  getGatewayMetrics() {
    return {
      primary: this.primaryGateway.getMetrics(),
      fallback: this.fallbackGateway.getMetrics()
    };
  }
}

// πŸš€ Usage example
const demonstratePaymentProcessing = async () => {
  const processor = new PaymentProcessor();
  
  const paymentRequests: PaymentRequest[] = [
    {
      orderId: 'order_001',
      amount: 99.99,
      currency: 'USD',
      paymentMethodId: 'pm_123',
      customerId: 'cust_456'
    },
    {
      orderId: 'order_002',
      amount: 149.50,
      currency: 'USD',
      paymentMethodId: 'pm_789',
      customerId: 'cust_012'
    },
    {
      orderId: 'order_003',
      amount: 299.00,
      currency: 'USD',
      paymentMethodId: 'pm_345',
      customerId: 'cust_678'
    }
  ];
  
  try {
    const batchResult = await processor.processPaymentBatch(paymentRequests);
    
    console.log('πŸ“Š Payment batch results:');
    console.log(`βœ… Successful: ${batchResult.successful.length}`);
    console.log(`❌ Failed: ${batchResult.failed.length}`);
    console.log(`πŸ’° Success rate: ${batchResult.summary.successRate.toFixed(1)}%`);
    console.log(`πŸ’΅ Total processed: $${batchResult.summary.successfulAmount.toFixed(2)}`);
    
    // πŸ“ˆ Gateway metrics
    const metrics = processor.getGatewayMetrics();
    console.log('πŸ”Œ Gateway metrics:', metrics);
    
  } catch (error) {
    console.error('πŸ’₯ Batch processing failed:', error);
  }
};

🏁 Conclusion

Congratulations! πŸŽ‰ You’ve mastered the art of bulletproof async error handling in TypeScript. You now have the skills to:

  • βœ… Structure robust try/catch blocks for comprehensive error management
  • 🚨 Design custom error hierarchies for better error classification and handling
  • πŸ”„ Implement intelligent retry mechanisms with exponential backoff and jitter
  • πŸ›‘οΈ Build resilient systems with circuit breakers and error boundaries
  • πŸ’³ Handle real-world scenarios like payment processing with multiple failure modes

You’ve learned to build async systems that don’t just work, but gracefully handle failures and recover automatically. Keep practicing these patterns, and you’ll be the reliability engineer your team needs! πŸ†

πŸ”— Next Steps

Ready to level up further? Check out these advanced topics:

  • 🌊 Async Iterators for streaming data with error handling
  • πŸ“‘ Event-Driven Error Handling with EventEmitters
  • 🏭 Distributed System Patterns like bulkheads and timeouts
  • πŸ“Š Error Monitoring with APM tools and alerting
  • πŸ”„ Chaos Engineering for testing failure scenarios