Claude Code Plugins

Community-maintained marketplace

Feedback

error-handling-patterns

@chriscarterux/chris-claude-stack
1
0

This skill should be used when implementing robust error handling strategies - try-catch patterns, error boundaries, retry logic, circuit breakers, and graceful degradation.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name error-handling-patterns
description This skill should be used when implementing robust error handling strategies - try-catch patterns, error boundaries, retry logic, circuit breakers, and graceful degradation.

Error Handling Patterns

Overview

Robust error handling is the difference between a system that crashes under pressure and one that gracefully degrades. This skill covers comprehensive error handling strategies including try-catch patterns, error boundaries, retry logic with exponential backoff, circuit breakers, and graceful degradation techniques.

Core Principle: Errors are inevitable. The goal is not to prevent all errors, but to handle them gracefully, recover when possible, and fail safely when recovery isn't possible.

When to Use

Apply these patterns when:

  • Building production APIs that must handle failures gracefully
  • Implementing client-side applications that consume unreliable external services
  • Creating systems that require high availability and fault tolerance
  • Working with asynchronous operations, network requests, or third-party integrations
  • Building microservices that need to remain resilient when dependencies fail
  • Implementing user-facing features that should never crash the entire application
  • Setting up monitoring and alerting for production systems
  • Designing systems that need to recover from transient failures automatically

Error Handling Strategies

Try-Catch Patterns

Basic Synchronous Error Handling:

function processUserData(data: unknown): User {
  try {
    // Validate and parse data
    const validated = userSchema.parse(data);

    // Transform data
    return transformToUser(validated);
  } catch (error) {
    if (error instanceof ZodError) {
      // Handle validation errors
      throw new ValidationError('Invalid user data', error.errors);
    }

    // Log unexpected errors
    logger.error('Unexpected error processing user data', { error, data });
    throw new ProcessingError('Failed to process user data');
  }
}

Nested Try-Catch for Specific Error Handling:

async function saveUserWithProfileImage(user: User, imageFile: File): Promise<void> {
  let savedUser: User | null = null;

  try {
    // First operation - save user
    try {
      savedUser = await database.users.create(user);
    } catch (error) {
      if (error.code === 'DUPLICATE_KEY') {
        throw new ConflictError('User already exists');
      }
      throw error; // Re-throw unknown errors
    }

    // Second operation - upload image
    try {
      const imageUrl = await s3.upload(imageFile, savedUser.id);
      await database.users.update(savedUser.id, { profileImage: imageUrl });
    } catch (error) {
      // Rollback user creation if image upload fails
      await database.users.delete(savedUser.id);
      throw new ImageUploadError('Failed to upload profile image', { cause: error });
    }

  } catch (error) {
    // Top-level error handling and logging
    logger.error('Failed to save user with profile image', {
      error,
      userId: savedUser?.id,
      hasRollback: !!savedUser
    });
    throw error;
  }
}

Error Boundaries (React)

Comprehensive Error Boundary Component:

import React, { Component, ErrorInfo, ReactNode } from 'react';

interface Props {
  children: ReactNode;
  fallback?: ReactNode;
  onError?: (error: Error, errorInfo: ErrorInfo) => void;
  isolate?: boolean; // Prevent error from bubbling up
}

interface State {
  hasError: boolean;
  error: Error | null;
  errorInfo: ErrorInfo | null;
}

class ErrorBoundary extends Component<Props, State> {
  constructor(props: Props) {
    super(props);
    this.state = {
      hasError: false,
      error: null,
      errorInfo: null,
    };
  }

  static getDerivedStateFromError(error: Error): Partial<State> {
    return { hasError: true, error };
  }

  componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
    // Log error to monitoring service
    logger.error('React Error Boundary caught error', {
      error: error.toString(),
      stack: errorInfo.componentStack,
      componentStack: errorInfo.componentStack,
    });

    // Send to error tracking
    if (typeof window !== 'undefined' && window.Sentry) {
      window.Sentry.captureException(error, {
        contexts: {
          react: {
            componentStack: errorInfo.componentStack,
          },
        },
      });
    }

    // Call custom error handler
    this.props.onError?.(error, errorInfo);

    // Update state
    this.setState({ errorInfo });

    // Prevent bubbling if isolate is true
    if (!this.props.isolate) {
      throw error;
    }
  }

  resetError = (): void => {
    this.setState({
      hasError: false,
      error: null,
      errorInfo: null,
    });
  };

  render(): ReactNode {
    if (this.state.hasError) {
      if (this.props.fallback) {
        return this.props.fallback;
      }

      return (
        <div className="error-boundary-fallback">
          <h2>Something went wrong</h2>
          <details style={{ whiteSpace: 'pre-wrap' }}>
            {this.state.error?.toString()}
            <br />
            {this.state.errorInfo?.componentStack}
          </details>
          <button onClick={this.resetError}>Try again</button>
        </div>
      );
    }

    return this.props.children;
  }
}

// Usage
function App() {
  return (
    <ErrorBoundary
      fallback={<ErrorFallback />}
      onError={(error, info) => analytics.track('ui_error', { error, info })}
    >
      <Dashboard />
    </ErrorBoundary>
  );
}

Promise Rejection Handling

Comprehensive Promise Error Handling:

// Avoid: Unhandled promise rejections
async function badExample() {
  fetch('/api/data'); // No error handling - dangerous!
}

// Good: Always handle promise rejections
async function goodExample() {
  try {
    const response = await fetch('/api/data');
    if (!response.ok) {
      throw new HTTPError(`HTTP ${response.status}: ${response.statusText}`, response.status);
    }
    return await response.json();
  } catch (error) {
    if (error instanceof TypeError) {
      throw new NetworkError('Network request failed', { cause: error });
    }
    throw error;
  }
}

// Promise chain error handling
function promiseChainExample() {
  return fetch('/api/data')
    .then(response => {
      if (!response.ok) {
        throw new HTTPError(`HTTP ${response.status}`, response.status);
      }
      return response.json();
    })
    .then(data => processData(data))
    .catch(error => {
      if (error instanceof HTTPError && error.status === 404) {
        return null; // Return default for not found
      }
      logger.error('Failed to fetch and process data', { error });
      throw error;
    });
}

// Global unhandled rejection handler
if (typeof window !== 'undefined') {
  window.addEventListener('unhandledrejection', (event) => {
    logger.error('Unhandled promise rejection', {
      reason: event.reason,
      promise: event.promise,
    });

    // Send to error tracking
    Sentry.captureException(event.reason);

    // Prevent default browser behavior
    event.preventDefault();
  });
}

Async/Await Error Patterns

Advanced Async Error Handling:

// Pattern 1: Result type for controlled error handling
type Result<T, E = Error> =
  | { success: true; data: T }
  | { success: false; error: E };

async function safeAsync<T>(
  promise: Promise<T>
): Promise<Result<T>> {
  try {
    const data = await promise;
    return { success: true, data };
  } catch (error) {
    return { success: false, error: error as Error };
  }
}

// Usage
async function fetchUserSafely(id: string) {
  const result = await safeAsync(api.getUser(id));

  if (!result.success) {
    logger.error('Failed to fetch user', { error: result.error, userId: id });
    return null;
  }

  return result.data;
}

// Pattern 2: Parallel async with error aggregation
async function fetchMultipleResources(ids: string[]): Promise<{
  successful: Resource[];
  failed: Array<{ id: string; error: Error }>;
}> {
  const results = await Promise.allSettled(
    ids.map(id => api.getResource(id))
  );

  const successful: Resource[] = [];
  const failed: Array<{ id: string; error: Error }> = [];

  results.forEach((result, index) => {
    if (result.status === 'fulfilled') {
      successful.push(result.value);
    } else {
      failed.push({ id: ids[index], error: result.reason });
      logger.warn('Resource fetch failed', {
        id: ids[index],
        error: result.reason
      });
    }
  });

  return { successful, failed };
}

Resilience Patterns

Retry with Exponential Backoff

Production-Ready Retry Implementation:

interface RetryOptions {
  maxRetries: number;
  initialDelay: number;
  maxDelay: number;
  backoffMultiplier: number;
  shouldRetry?: (error: Error, attempt: number) => boolean;
  onRetry?: (error: Error, attempt: number, delay: number) => void;
}

const DEFAULT_RETRY_OPTIONS: RetryOptions = {
  maxRetries: 3,
  initialDelay: 1000,
  maxDelay: 30000,
  backoffMultiplier: 2,
  shouldRetry: (error) => {
    // Retry on network errors and 5xx status codes
    if (error instanceof NetworkError) return true;
    if (error instanceof HTTPError) {
      return error.status >= 500 && error.status < 600;
    }
    return false;
  },
};

async function withRetry<T>(
  fn: () => Promise<T>,
  options: Partial<RetryOptions> = {}
): Promise<T> {
  const opts = { ...DEFAULT_RETRY_OPTIONS, ...options };
  let lastError: Error;

  for (let attempt = 1; attempt <= opts.maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      lastError = error as Error;

      // Check if we should retry
      const shouldRetry = opts.shouldRetry?.(lastError, attempt) ?? true;
      const isLastAttempt = attempt === opts.maxRetries;

      if (!shouldRetry || isLastAttempt) {
        throw lastError;
      }

      // Calculate delay with exponential backoff and jitter
      const exponentialDelay = Math.min(
        opts.initialDelay * Math.pow(opts.backoffMultiplier, attempt - 1),
        opts.maxDelay
      );
      const jitter = exponentialDelay * 0.1 * Math.random();
      const delay = exponentialDelay + jitter;

      // Call retry callback
      opts.onRetry?.(lastError, attempt, delay);

      logger.info('Retrying operation', {
        attempt,
        maxRetries: opts.maxRetries,
        delay,
        error: lastError.message,
      });

      // Wait before retrying
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }

  throw lastError!;
}

// Usage
async function fetchDataWithRetry(url: string) {
  return withRetry(
    () => fetch(url).then(r => r.json()),
    {
      maxRetries: 5,
      initialDelay: 500,
      onRetry: (error, attempt, delay) => {
        logger.warn('Retrying fetch', { url, attempt, delay, error });
      },
    }
  );
}

Circuit Breaker

Complete Circuit Breaker Implementation:

enum CircuitState {
  CLOSED = 'CLOSED',     // Normal operation
  OPEN = 'OPEN',         // Failing, reject requests
  HALF_OPEN = 'HALF_OPEN' // Testing if service recovered
}

interface CircuitBreakerOptions {
  failureThreshold: number;      // Failures before opening
  successThreshold: number;      // Successes to close from half-open
  timeout: number;               // Time to wait before half-open (ms)
  rollingWindowSize: number;     // Window for counting failures
  onStateChange?: (from: CircuitState, to: CircuitState) => void;
}

class CircuitBreaker {
  private state: CircuitState = CircuitState.CLOSED;
  private failures: number[] = []; // Timestamps of failures
  private successes = 0;
  private nextAttempt = 0;

  constructor(
    private name: string,
    private options: CircuitBreakerOptions
  ) {}

  async execute<T>(fn: () => Promise<T>): Promise<T> {
    if (this.state === CircuitState.OPEN) {
      if (Date.now() < this.nextAttempt) {
        throw new CircuitBreakerOpenError(
          `Circuit breaker ${this.name} is OPEN`
        );
      }
      this.setState(CircuitState.HALF_OPEN);
    }

    try {
      const result = await fn();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }

  private onSuccess(): void {
    this.failures = [];

    if (this.state === CircuitState.HALF_OPEN) {
      this.successes++;

      if (this.successes >= this.options.successThreshold) {
        this.setState(CircuitState.CLOSED);
        this.successes = 0;
      }
    }
  }

  private onFailure(): void {
    const now = Date.now();
    this.failures.push(now);
    this.successes = 0;

    // Remove old failures outside rolling window
    const windowStart = now - this.options.rollingWindowSize;
    this.failures = this.failures.filter(t => t > windowStart);

    if (this.failures.length >= this.options.failureThreshold) {
      this.setState(CircuitState.OPEN);
      this.nextAttempt = now + this.options.timeout;
    }
  }

  private setState(newState: CircuitState): void {
    const oldState = this.state;
    this.state = newState;

    logger.info('Circuit breaker state change', {
      name: this.name,
      from: oldState,
      to: newState,
    });

    this.options.onStateChange?.(oldState, newState);
  }

  getState(): CircuitState {
    return this.state;
  }

  reset(): void {
    this.failures = [];
    this.successes = 0;
    this.setState(CircuitState.CLOSED);
  }
}

// Usage
const apiCircuitBreaker = new CircuitBreaker('external-api', {
  failureThreshold: 5,
  successThreshold: 2,
  timeout: 60000, // 1 minute
  rollingWindowSize: 120000, // 2 minutes
  onStateChange: (from, to) => {
    metrics.increment('circuit_breaker.state_change', {
      name: 'external-api',
      from,
      to,
    });
  },
});

async function callExternalAPI(data: unknown) {
  return apiCircuitBreaker.execute(async () => {
    const response = await fetch('https://api.external.com/data', {
      method: 'POST',
      body: JSON.stringify(data),
    });

    if (!response.ok) {
      throw new HTTPError(`HTTP ${response.status}`, response.status);
    }

    return response.json();
  });
}

Bulkheads

Bulkhead Pattern for Resource Isolation:

class Bulkhead {
  private activeRequests = 0;
  private queue: Array<{
    resolve: (value: void) => void;
    reject: (reason: Error) => void;
  }> = [];

  constructor(
    private maxConcurrent: number,
    private maxQueueSize: number = Infinity
  ) {}

  async execute<T>(fn: () => Promise<T>): Promise<T> {
    await this.acquire();

    try {
      return await fn();
    } finally {
      this.release();
    }
  }

  private async acquire(): Promise<void> {
    if (this.activeRequests < this.maxConcurrent) {
      this.activeRequests++;
      return;
    }

    if (this.queue.length >= this.maxQueueSize) {
      throw new BulkheadRejectError('Bulkhead queue is full');
    }

    return new Promise((resolve, reject) => {
      this.queue.push({ resolve, reject });
    });
  }

  private release(): void {
    const next = this.queue.shift();

    if (next) {
      next.resolve();
    } else {
      this.activeRequests--;
    }
  }
}

// Usage: Isolate database connections from API calls
const dbBulkhead = new Bulkhead(10, 50); // Max 10 concurrent, queue 50
const apiBulkhead = new Bulkhead(20, 100);

async function queryDatabase(query: string) {
  return dbBulkhead.execute(async () => {
    return database.query(query);
  });
}

async function callExternalService(url: string) {
  return apiBulkhead.execute(async () => {
    return fetch(url);
  });
}

Timeouts

Timeout Pattern with Cancellation:

class TimeoutError extends Error {
  constructor(message: string, public duration: number) {
    super(message);
    this.name = 'TimeoutError';
  }
}

function withTimeout<T>(
  promise: Promise<T>,
  timeoutMs: number,
  timeoutMessage?: string
): Promise<T> {
  let timeoutId: NodeJS.Timeout;

  const timeoutPromise = new Promise<never>((_, reject) => {
    timeoutId = setTimeout(() => {
      reject(new TimeoutError(
        timeoutMessage || `Operation timed out after ${timeoutMs}ms`,
        timeoutMs
      ));
    }, timeoutMs);
  });

  return Promise.race([promise, timeoutPromise]).finally(() => {
    clearTimeout(timeoutId);
  });
}

// Usage with AbortController for actual cancellation
async function fetchWithTimeout(url: string, timeoutMs: number) {
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);

  try {
    const response = await fetch(url, { signal: controller.signal });
    return await response.json();
  } catch (error) {
    if (error.name === 'AbortError') {
      throw new TimeoutError(`Request to ${url} timed out`, timeoutMs);
    }
    throw error;
  } finally {
    clearTimeout(timeoutId);
  }
}

Error Types and Classification

Transient Errors

Identifying and Handling Transient Errors:

class TransientError extends Error {
  constructor(message: string, public cause?: Error) {
    super(message);
    this.name = 'TransientError';
  }
}

function isTransientError(error: Error): boolean {
  // Network errors are usually transient
  if (error instanceof NetworkError) return true;

  // HTTP 5xx errors are often transient
  if (error instanceof HTTPError) {
    return error.status >= 500 && error.status < 600;
  }

  // Rate limiting is transient
  if (error instanceof HTTPError && error.status === 429) return true;

  // Database connection errors are often transient
  if (error.message.includes('ECONNREFUSED')) return true;
  if (error.message.includes('ETIMEDOUT')) return true;

  return false;
}

async function handleWithErrorClassification<T>(
  fn: () => Promise<T>
): Promise<T> {
  try {
    return await fn();
  } catch (error) {
    if (isTransientError(error as Error)) {
      // Retry transient errors
      return withRetry(fn, { maxRetries: 3 });
    }

    // Don't retry permanent errors
    throw error;
  }
}

Permanent Errors

Permanent Error Types:

class PermanentError extends Error {
  constructor(message: string, public code: string) {
    super(message);
    this.name = 'PermanentError';
  }
}

class ValidationError extends PermanentError {
  constructor(message: string, public errors: unknown[]) {
    super(message, 'VALIDATION_ERROR');
  }
}

class AuthenticationError extends PermanentError {
  constructor(message: string) {
    super(message, 'AUTHENTICATION_ERROR');
  }
}

class AuthorizationError extends PermanentError {
  constructor(message: string, public requiredPermission: string) {
    super(message, 'AUTHORIZATION_ERROR');
  }
}

class NotFoundError extends PermanentError {
  constructor(resource: string, id: string) {
    super(`${resource} not found: ${id}`, 'NOT_FOUND');
  }
}

Business Logic Errors

Domain-Specific Error Handling:

class BusinessError extends Error {
  constructor(
    message: string,
    public code: string,
    public context?: Record<string, unknown>
  ) {
    super(message);
    this.name = 'BusinessError';
  }
}

class InsufficientFundsError extends BusinessError {
  constructor(required: number, available: number) {
    super(
      `Insufficient funds: required ${required}, available ${available}`,
      'INSUFFICIENT_FUNDS',
      { required, available }
    );
  }
}

class InventoryError extends BusinessError {
  constructor(productId: string, requested: number, available: number) {
    super(
      `Insufficient inventory for product ${productId}`,
      'INSUFFICIENT_INVENTORY',
      { productId, requested, available }
    );
  }
}

// Usage in business logic
async function processOrder(order: Order): Promise<OrderResult> {
  try {
    // Check inventory
    const inventory = await getInventory(order.productId);
    if (inventory.available < order.quantity) {
      throw new InventoryError(
        order.productId,
        order.quantity,
        inventory.available
      );
    }

    // Check funds
    const balance = await getBalance(order.userId);
    if (balance < order.total) {
      throw new InsufficientFundsError(order.total, balance);
    }

    // Process order
    return await createOrder(order);

  } catch (error) {
    if (error instanceof BusinessError) {
      // Business errors are expected, handle gracefully
      logger.info('Business rule violation', {
        code: error.code,
        context: error.context,
      });
      return { success: false, error: error.code, message: error.message };
    }

    // Unexpected errors should be logged and re-thrown
    logger.error('Unexpected error processing order', { error, order });
    throw error;
  }
}

Logging and Monitoring

Structured Error Logging

Production-Grade Error Logging:

interface ErrorLogContext {
  error: Error;
  level: 'error' | 'warn' | 'info';
  context?: Record<string, unknown>;
  user?: { id: string; email?: string };
  request?: { method: string; url: string; headers?: Record<string, string> };
}

class ErrorLogger {
  log(config: ErrorLogContext): void {
    const logEntry = {
      timestamp: new Date().toISOString(),
      level: config.level,
      message: config.error.message,
      error: {
        name: config.error.name,
        message: config.error.message,
        stack: config.error.stack,
        ...this.serializeError(config.error),
      },
      context: config.context,
      user: config.user,
      request: config.request,
      environment: process.env.NODE_ENV,
      service: process.env.SERVICE_NAME,
    };

    // Send to logging service
    console.log(JSON.stringify(logEntry));

    // Send to external monitoring
    if (config.level === 'error') {
      this.sendToMonitoring(logEntry);
    }
  }

  private serializeError(error: Error): Record<string, unknown> {
    const serialized: Record<string, unknown> = {};

    // Include custom error properties
    Object.getOwnPropertyNames(error).forEach(key => {
      if (!['name', 'message', 'stack'].includes(key)) {
        serialized[key] = (error as any)[key];
      }
    });

    return serialized;
  }

  private sendToMonitoring(logEntry: unknown): void {
    // Integration with monitoring services
    // Sentry, Datadog, New Relic, etc.
  }
}

const errorLogger = new ErrorLogger();

// Usage in error handlers
app.use((error: Error, req: Request, res: Response, next: NextFunction) => {
  errorLogger.log({
    error,
    level: 'error',
    context: {
      requestId: req.id,
      path: req.path,
      method: req.method,
    },
    user: req.user ? { id: req.user.id, email: req.user.email } : undefined,
    request: {
      method: req.method,
      url: req.url,
      headers: req.headers as Record<string, string>,
    },
  });

  res.status(500).json({ error: 'Internal server error' });
});

Error Tracking (Sentry Integration)

Sentry Setup with Context:

import * as Sentry from '@sentry/node';

// Initialize Sentry
Sentry.init({
  dsn: process.env.SENTRY_DSN,
  environment: process.env.NODE_ENV,
  tracesSampleRate: 0.1,
  beforeSend(event, hint) {
    // Filter sensitive data
    if (event.request?.headers) {
      delete event.request.headers['authorization'];
      delete event.request.headers['cookie'];
    }

    // Add custom logic
    return event;
  },
});

// Capture errors with context
function captureError(error: Error, context?: Record<string, unknown>): void {
  Sentry.withScope((scope) => {
    // Add context
    if (context) {
      Object.entries(context).forEach(([key, value]) => {
        scope.setContext(key, value);
      });
    }

    // Add tags for filtering
    scope.setTag('error_type', error.name);

    // Set error level
    if (error instanceof BusinessError) {
      scope.setLevel('warning');
    } else {
      scope.setLevel('error');
    }

    Sentry.captureException(error);
  });
}

Alert Strategies

Smart Alerting Based on Error Patterns:

class AlertManager {
  private errorCounts = new Map<string, number[]>();

  shouldAlert(error: Error): boolean {
    const errorKey = `${error.name}:${error.message}`;
    const now = Date.now();
    const window = 5 * 60 * 1000; // 5 minutes

    // Get recent occurrences
    const occurrences = this.errorCounts.get(errorKey) || [];
    const recentOccurrences = occurrences.filter(t => t > now - window);

    // Add current occurrence
    recentOccurrences.push(now);
    this.errorCounts.set(errorKey, recentOccurrences);

    // Alert if error rate exceeds threshold
    const threshold = 10; // 10 errors in 5 minutes
    return recentOccurrences.length >= threshold;
  }
}

Recovery Strategies

Graceful Degradation

Fallback Implementation:

async function getRecommendations(userId: string): Promise<Product[]> {
  try {
    // Try ML-based recommendations
    return await withTimeout(
      mlService.getRecommendations(userId),
      2000 // 2 second timeout
    );
  } catch (error) {
    logger.warn('ML recommendations failed, using fallback', { error, userId });

    try {
      // Fallback to collaborative filtering
      return await collaborativeFiltering.getRecommendations(userId);
    } catch (error) {
      logger.warn('Collaborative filtering failed, using default', { error });

      // Final fallback: popular products
      return await getPopularProducts();
    }
  }
}

Fallback Mechanisms

Multi-Level Fallback Pattern:

interface FallbackConfig<T> {
  primary: () => Promise<T>;
  fallbacks: Array<() => Promise<T>>;
  default: T;
  onFallback?: (level: number, error: Error) => void;
}

async function withFallback<T>(config: FallbackConfig<T>): Promise<T> {
  const attempts = [config.primary, ...config.fallbacks];

  for (let i = 0; i < attempts.length; i++) {
    try {
      return await attempts[i]();
    } catch (error) {
      config.onFallback?.(i, error as Error);

      if (i === attempts.length - 1) {
        logger.error('All fallbacks exhausted', { error });
        return config.default;
      }
    }
  }

  return config.default;
}

// Usage
const userData = await withFallback({
  primary: () => cache.get(userId),
  fallbacks: [
    () => database.users.findById(userId),
    () => legacyAPI.getUser(userId),
  ],
  default: { id: userId, name: 'Guest' },
  onFallback: (level, error) => {
    metrics.increment('user_fetch.fallback', { level });
  },
});

Data Recovery

Transaction Rollback and Recovery:

async function performComplexTransaction(data: TransactionData) {
  const operations: Array<() => Promise<void>> = [];

  try {
    // Operation 1: Update inventory
    await database.inventory.decrement(data.productId, data.quantity);
    operations.push(() =>
      database.inventory.increment(data.productId, data.quantity)
    );

    // Operation 2: Charge payment
    const paymentId = await paymentService.charge(data.userId, data.amount);
    operations.push(() =>
      paymentService.refund(paymentId)
    );

    // Operation 3: Create order
    const orderId = await database.orders.create(data);
    operations.push(() =>
      database.orders.delete(orderId)
    );

    // Operation 4: Send confirmation
    await emailService.send(data.email, 'Order confirmed');

    return { success: true, orderId };

  } catch (error) {
    logger.error('Transaction failed, rolling back', { error, data });

    // Rollback in reverse order
    for (const rollback of operations.reverse()) {
      try {
        await rollback();
      } catch (rollbackError) {
        logger.error('Rollback operation failed', {
          rollbackError,
          originalError: error
        });
      }
    }

    throw new TransactionError('Transaction failed and was rolled back', {
      cause: error,
    });
  }
}

Anti-Patterns

Common Mistakes to Avoid:

  1. Silent Failures
// WRONG: Swallowing errors
try {
  await riskyOperation();
} catch (error) {
  // Nothing - error is lost!
}

// RIGHT: Always log or handle
try {
  await riskyOperation();
} catch (error) {
  logger.error('Risky operation failed', { error });
  throw error; // Or handle appropriately
}
  1. Generic Error Handlers
// WRONG: Treating all errors the same
catch (error) {
  return res.status(500).json({ error: 'Something went wrong' });
}

// RIGHT: Handle different error types
catch (error) {
  if (error instanceof ValidationError) {
    return res.status(400).json({ error: error.message, details: error.errors });
  }
  if (error instanceof NotFoundError) {
    return res.status(404).json({ error: error.message });
  }
  logger.error('Unexpected error', { error });
  return res.status(500).json({ error: 'Internal server error' });
}
  1. Retry Without Backoff
// WRONG: Immediate retry hammers the service
for (let i = 0; i < 3; i++) {
  try {
    return await callAPI();
  } catch (error) {
    if (i === 2) throw error;
  }
}

// RIGHT: Use exponential backoff
return withRetry(callAPI, { maxRetries: 3, initialDelay: 1000 });
  1. Exposing Internal Errors
// WRONG: Leaking implementation details
res.status(500).json({
  error: error.stack,
  query: 'SELECT * FROM users WHERE password = ...'
});

// RIGHT: Safe error messages
res.status(500).json({
  error: 'An unexpected error occurred',
  requestId: req.id // For support reference
});
  1. Missing Error Boundaries
// WRONG: No error boundary
function App() {
  return <Dashboard />; // Entire app crashes on error
}

// RIGHT: Isolated error boundaries
function App() {
  return (
    <ErrorBoundary>
      <Dashboard />
    </ErrorBoundary>
  );
}

Examples

Example 1: API Error Handling Middleware

// Express error handling middleware
function errorHandler(
  error: Error,
  req: Request,
  res: Response,
  next: NextFunction
): void {
  // Log all errors
  errorLogger.log({
    error,
    level: 'error',
    context: { requestId: req.id },
    request: {
      method: req.method,
      url: req.url,
    },
  });

  // Handle known error types
  if (error instanceof ValidationError) {
    res.status(400).json({
      error: 'Validation failed',
      details: error.errors,
    });
    return;
  }

  if (error instanceof AuthenticationError) {
    res.status(401).json({
      error: 'Authentication required',
    });
    return;
  }

  if (error instanceof AuthorizationError) {
    res.status(403).json({
      error: 'Insufficient permissions',
    });
    return;
  }

  if (error instanceof NotFoundError) {
    res.status(404).json({
      error: error.message,
    });
    return;
  }

  if (error instanceof ConflictError) {
    res.status(409).json({
      error: error.message,
    });
    return;
  }

  // Default to 500 for unknown errors
  res.status(500).json({
    error: 'An unexpected error occurred',
    requestId: req.id,
  });
}

Example 2: Resilient API Client

class ResilientAPIClient {
  private circuitBreaker: CircuitBreaker;

  constructor(private baseURL: string) {
    this.circuitBreaker = new CircuitBreaker('api-client', {
      failureThreshold: 5,
      successThreshold: 2,
      timeout: 30000,
      rollingWindowSize: 60000,
    });
  }

  async get<T>(path: string, options: RequestOptions = {}): Promise<T> {
    return this.request<T>('GET', path, options);
  }

  async post<T>(path: string, data: unknown, options: RequestOptions = {}): Promise<T> {
    return this.request<T>('POST', path, { ...options, body: data });
  }

  private async request<T>(
    method: string,
    path: string,
    options: RequestOptions
  ): Promise<T> {
    // Wrap in circuit breaker
    return this.circuitBreaker.execute(async () => {
      // Retry with exponential backoff
      return withRetry(
        async () => {
          // Add timeout
          return withTimeout(
            this.executeRequest<T>(method, path, options),
            options.timeout || 10000
          );
        },
        {
          maxRetries: 3,
          initialDelay: 1000,
          shouldRetry: (error) => isTransientError(error),
        }
      );
    });
  }

  private async executeRequest<T>(
    method: string,
    path: string,
    options: RequestOptions
  ): Promise<T> {
    const url = `${this.baseURL}${path}`;

    const response = await fetch(url, {
      method,
      headers: {
        'Content-Type': 'application/json',
        ...options.headers,
      },
      body: options.body ? JSON.stringify(options.body) : undefined,
    });

    if (!response.ok) {
      throw new HTTPError(
        `HTTP ${response.status}: ${response.statusText}`,
        response.status
      );
    }

    return response.json();
  }
}

Example 3: Database Query with Error Recovery

async function executeQueryWithRecovery<T>(
  query: string,
  params: unknown[]
): Promise<T> {
  try {
    // Try primary database
    return await primaryDB.query<T>(query, params);
  } catch (error) {
    logger.error('Primary database query failed', { error, query });

    // Check if error is recoverable
    if (isConnectionError(error)) {
      logger.info('Attempting to reconnect to database');
      await primaryDB.reconnect();

      try {
        return await primaryDB.query<T>(query, params);
      } catch (retryError) {
        logger.error('Retry on primary DB failed', { retryError });
      }
    }

    // Try read replica for SELECT queries
    if (query.trim().toUpperCase().startsWith('SELECT')) {
      logger.info('Attempting query on read replica');
      try {
        return await replicaDB.query<T>(query, params);
      } catch (replicaError) {
        logger.error('Read replica query failed', { replicaError });
      }
    }

    // All recovery attempts failed
    throw new DatabaseError('All database query attempts failed', {
      cause: error,
    });
  }
}

Example 4: React Data Fetching with Error Handling

function useDataWithErrorHandling<T>(url: string) {
  const [data, setData] = useState<T | null>(null);
  const [error, setError] = useState<Error | null>(null);
  const [loading, setLoading] = useState(true);
  const [retryCount, setRetryCount] = useState(0);

  useEffect(() => {
    let cancelled = false;

    async function fetchData() {
      setLoading(true);
      setError(null);

      try {
        const result = await withRetry(
          async () => {
            const response = await fetch(url);
            if (!response.ok) {
              throw new HTTPError(`HTTP ${response.status}`, response.status);
            }
            return response.json();
          },
          {
            maxRetries: 3,
            initialDelay: 1000,
            onRetry: (error, attempt) => {
              console.log(`Retry attempt ${attempt}`, error);
            },
          }
        );

        if (!cancelled) {
          setData(result);
          setError(null);
        }
      } catch (err) {
        if (!cancelled) {
          setError(err as Error);

          // Log to monitoring
          captureError(err as Error, { url, retryCount });
        }
      } finally {
        if (!cancelled) {
          setLoading(false);
        }
      }
    }

    fetchData();

    return () => {
      cancelled = true;
    };
  }, [url, retryCount]);

  const retry = useCallback(() => {
    setRetryCount(count => count + 1);
  }, []);

  return { data, error, loading, retry };
}

// Usage in component
function DataDisplay() {
  const { data, error, loading, retry } = useDataWithErrorHandling('/api/data');

  if (loading) return <LoadingSpinner />;

  if (error) {
    return (
      <ErrorMessage>
        <p>Failed to load data: {error.message}</p>
        <button onClick={retry}>Retry</button>
      </ErrorMessage>
    );
  }

  return <DataView data={data} />;
}

Example 5: Comprehensive Error Handling in Async Operations

async function processUserRegistration(userData: UserRegistrationData) {
  const operations: Array<{ name: string; rollback: () => Promise<void> }> = [];

  try {
    // Step 1: Validate input
    const validated = await validateUserData(userData);

    // Step 2: Create user account
    const user = await database.users.create(validated);
    operations.push({
      name: 'create_user',
      rollback: async () => {
        await database.users.delete(user.id);
        logger.info('Rolled back user creation', { userId: user.id });
      },
    });

    // Step 3: Create auth credentials
    const authId = await authService.createCredentials(user.id, validated.password);
    operations.push({
      name: 'create_auth',
      rollback: async () => {
        await authService.deleteCredentials(authId);
        logger.info('Rolled back auth creation', { authId });
      },
    });

    // Step 4: Send welcome email (with retry)
    await withRetry(
      () => emailService.sendWelcome(user.email),
      {
        maxRetries: 3,
        initialDelay: 1000,
        shouldRetry: (error) => error instanceof NetworkError,
      }
    );

    // Step 5: Create initial profile
    await database.profiles.create({
      userId: user.id,
      displayName: validated.displayName,
    });

    logger.info('User registration completed', { userId: user.id });

    return { success: true, userId: user.id };

  } catch (error) {
    logger.error('User registration failed', { error, userData: userData.email });

    // Rollback all operations in reverse order
    for (const op of operations.reverse()) {
      try {
        await op.rollback();
      } catch (rollbackError) {
        logger.error('Rollback failed', {
          operation: op.name,
          rollbackError,
          originalError: error,
        });

        // Send alert for failed rollback
        await alerting.sendCritical('Rollback failed during user registration', {
          operation: op.name,
          error: rollbackError,
        });
      }
    }

    // Classify and throw appropriate error
    if (error instanceof ValidationError) {
      throw error;
    }

    if (error.code === 'DUPLICATE_EMAIL') {
      throw new ConflictError('Email already registered');
    }

    throw new RegistrationError('Failed to complete user registration', {
      cause: error,
    });
  }
}