Error Handling Guide

April 11, 2025 ยท View on GitHub

Result Type

The Result<T, E> type provides type-safe error handling:

class Result<T, E> {
  // Create a success result
  static Ok<T, E>(value: T): Result<T, E>;

  // Create an error result
  static Err<T, E>(error: E): Result<T, E>;

  // Check if result is success
  isOk(): boolean;

  // Check if result is error
  isErr(): boolean;

  // Get value if Ok, throw if Err
  unwrap(): T;

  // Get error if Err, throw if Ok
  unwrapErr(): E;
}

Basic Usage

function divide(a: number, b: number): Result<number, Error> {
  if (b === 0) {
    return Result.Err(new Error("Division by zero"));
  }
  return Result.Ok(a / b);
}

// Usage
const result = divide(10, 2);
if (result.isOk()) {
  console.log(result.unwrap());
} else {
  console.error(result.unwrapErr().message);
}

Pattern Matching

Use pattern matching for elegant error handling:

match result {
  Ok(value) => handleSuccess(value),
  Err(DatabaseError(e)) => handleDatabaseError(e),
  Err(NetworkError(e)) => handleNetworkError(e),
  Err(_) => handleUnknownError()
}

Async Error Handling

With Result Type

async function fetchUser(id: string): Promise<Result<User, Error>> {
  try {
    const response = await fetch(`/api/users/${id}`);
    if (!response.ok) {
      return Result.Err(new Error(`HTTP ${response.status}`));
    }
    const user = await response.json();
    return Result.Ok(user);
  } catch (error) {
    return Result.Err(error as Error);
  }
}

// Usage
const result = await fetchUser("123");
if (result.isOk()) {
  const user = result.unwrap();
  processUser(user);
} else {
  logError(result.unwrapErr());
}

Error Recovery

async function fetchWithRetry<T>(
  fn: () => Promise<T>,
  retries: number = 3
): Promise<Result<T, Error>> {
  for (let i = 0; i < retries; i++) {
    try {
      const result = await fn();
      return Result.Ok(result);
    } catch (error) {
      if (i === retries - 1) {
        return Result.Err(error as Error);
      }
      await delay(Math.pow(2, i) * 1000); // Exponential backoff
    }
  }
  return Result.Err(new Error("Max retries exceeded"));
}

Custom Error Types

Define Custom Errors

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

class ValidationError extends Error {
  constructor(message: string, public field: string) {
    super(message);
    this.name = "ValidationError";
  }
}

Type-Safe Error Handling

type ApiError = DatabaseError | ValidationError | NetworkError;

function handleApiError(error: ApiError) {
  switch (error.constructor) {
    case DatabaseError:
      handleDatabaseError(error as DatabaseError);
      break;
    case ValidationError:
      handleValidationError(error as ValidationError);
      break;
    case NetworkError:
      handleNetworkError(error as NetworkError);
      break;
  }
}

Error Boundaries

Component Error Boundaries

class ErrorBoundary {
  private errorHandler: (error: Error) => void;

  constructor(handler: (error: Error) => void) {
    this.errorHandler = handler;
  }

  wrap<T>(fn: () => T): T {
    try {
      return fn();
    } catch (error) {
      this.errorHandler(error as Error);
      throw error;
    }
  }
}

Usage

const boundary = new ErrorBoundary(error => {
  console.error("Component error:", error);
  showErrorUI(error);
});

boundary.wrap(() => {
  // Component code that might throw
  render();
});

Logging & Monitoring

Error Logging

class ErrorLogger {
  static log(error: Error, context?: any) {
    console.error({
      name: error.name,
      message: error.message,
      stack: error.stack,
      context,
      timestamp: new Date()
    });
  }
}

Error Monitoring

class ErrorMonitor {
  private static errors: Error[] = [];
  private static threshold = 10;

  static track(error: Error) {
    this.errors.push(error);
    if (this.errors.length >= this.threshold) {
      this.notify();
    }
  }

  private static notify() {
    // Send error report to monitoring service
    sendErrorReport(this.errors);
    this.errors = [];
  }
}

Best Practices

  1. Use Result<T, E> for expected error cases
  2. Use try/catch for unexpected errors
  3. Implement proper error recovery strategies
  4. Create custom error types for different error categories
  5. Set up error boundaries for component isolation
  6. Implement comprehensive error logging
  7. Monitor error patterns in production

Configuration Example

{
  "errorHandling": {
    "logging": {
      "level": "error",
      "format": "json",
      "destination": "file"
    },
    "monitoring": {
      "enabled": true,
      "threshold": 10,
      "reportUrl": "/api/errors"
    }
  }
}

See Also