or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

array.mdcompat.mderror.mdfunction.mdindex.mdmath.mdobject.mdpredicate.mdpromise.mdstring.mdutil.md
tile.json

error.mddocs/

Error Handling

Custom error classes and error handling utilities for robust application development. Provides structured error types and functional error handling patterns.

Capabilities

Custom Error Classes

Specialized error classes for common error scenarios in asynchronous and operational contexts.

/**
 * Error representing aborted operations
 */
class AbortError extends Error {
  /**
   * Creates new AbortError
   * @param message - Error message (default: 'The operation was aborted')
   */
  constructor(message?: string);
  
  /**
   * Error name identifier
   */
  name: 'AbortError';
}

/**
 * Error representing timed out operations
 */
class TimeoutError extends Error {
  /**
   * Creates new TimeoutError
   * @param message - Error message (default: 'The operation was timed out')
   */
  constructor(message?: string);
  
  /**
   * Error name identifier
   */
  name: 'TimeoutError';
}

Usage Examples:

import { AbortError, TimeoutError } from 'es-toolkit/error';

// Abortable operations
async function fetchWithAbort(url: string, signal?: AbortSignal): Promise<Response> {
  if (signal?.aborted) {
    throw new AbortError('Request was aborted before starting');
  }
  
  try {
    const response = await fetch(url, { signal });
    return response;
  } catch (error) {
    if (error.name === 'AbortError') {
      throw new AbortError(`Request to ${url} was aborted`);
    }
    throw error;
  }
}

// Timeout operations
async function operationWithTimeout<T>(
  operation: () => Promise<T>,
  timeoutMs: number
): Promise<T> {
  return Promise.race([
    operation(),
    new Promise<never>((_, reject) => {
      setTimeout(() => {
        reject(new TimeoutError(`Operation timed out after ${timeoutMs}ms`));
      }, timeoutMs);
    })
  ]);
}

// Usage examples
async function examples() {
  const controller = new AbortController();
  
  // Abort after 2 seconds
  setTimeout(() => controller.abort(), 2000);
  
  try {
    const response = await fetchWithAbort(
      'https://api.example.com/slow-endpoint',
      controller.signal
    );
    console.log('Request completed');
  } catch (error) {
    if (error instanceof AbortError) {
      console.log('Request was cancelled:', error.message);
    } else {
      console.log('Request failed:', error.message);
    }
  }
  
  // Timeout example
  try {
    const result = await operationWithTimeout(
      () => new Promise(resolve => setTimeout(() => resolve('done'), 5000)),
      3000
    );
    console.log('Operation result:', result);
  } catch (error) {
    if (error instanceof TimeoutError) {
      console.log('Operation timed out:', error.message);
    }
  }
}

Functional Error Handling

Utilities for handling errors using functional programming patterns, avoiding traditional try-catch blocks.

/**
 * Executes function and returns result or error as tuple (synchronous)
 * @param func - Function to execute
 * @returns Tuple of [error, null] or [null, result]
 */
function attempt<T, E = Error>(func: () => T): [E, null] | [null, T];

/**
 * Executes async function and returns result or error as tuple (asynchronous)
 * @param func - Async function to execute
 * @returns Promise resolving to tuple of [error, null] or [null, result]
 */
function attemptAsync<T, E = Error>(func: () => Promise<T>): Promise<[E, null] | [null, T]>;

/**
 * Asserts that condition is truthy, throws error if false
 * @param condition - Condition to check
 * @param message - Error message or Error instance to throw
 * @throws Error if condition is falsy
 */
function invariant(condition: unknown, message: string | Error): asserts condition;

/**
 * Alias for invariant function
 * @param condition - Condition to check
 * @param message - Error message or Error instance to throw
 * @throws Error if condition is falsy
 */
function assert(condition: unknown, message: string | Error): asserts condition;

Usage Examples:

import { attempt, attemptAsync, invariant, assert } from 'es-toolkit/error';

// Synchronous error handling with attempt
function parseConfig(configText: string) {
  const [error, config] = attempt(() => JSON.parse(configText));
  
  if (error) {
    console.error('Failed to parse config:', error.message);
    return null;
  }
  
  // TypeScript knows config is the parsed result here
  return config;
}

// Multiple operations with error handling
function processUserData(userData: string) {
  // Parse JSON
  const [parseError, data] = attempt(() => JSON.parse(userData));
  if (parseError) {
    return { success: false, error: 'Invalid JSON format' };
  }
  
  // Validate required fields
  const [validationError] = attempt(() => {
    invariant(data.name, 'Name is required');
    invariant(data.email, 'Email is required');
    invariant(typeof data.age === 'number', 'Age must be a number');
  });
  
  if (validationError) {
    return { success: false, error: validationError.message };
  }
  
  return { success: true, data };
}

// Asynchronous error handling
async function fetchUserProfile(userId: string) {
  const [error, response] = await attemptAsync(async () => {
    const res = await fetch(`/api/users/${userId}`);
    if (!res.ok) {
      throw new Error(`HTTP ${res.status}: ${res.statusText}`);
    }
    return res.json();
  });
  
  if (error) {
    console.error('Failed to fetch user profile:', error.message);
    return null;
  }
  
  return response;
}

// Database operations with error handling
async function createUser(userData: any) {
  // Validate input
  const [validationError] = attempt(() => {
    assert(userData.email, 'Email is required');
    assert(userData.name, 'Name is required');
    assert(userData.email.includes('@'), 'Invalid email format');
  });
  
  if (validationError) {
    return { success: false, error: validationError.message };
  }
  
  // Attempt database insertion
  const [dbError, user] = await attemptAsync(async () => {
    const result = await database.users.create(userData);
    return result;
  });
  
  if (dbError) {
    return { success: false, error: 'Database error: ' + dbError.message };
  }
  
  return { success: true, data: user };
}

// File operations
async function readConfigFile(filePath: string) {
  const [readError, content] = await attemptAsync(() => 
    fs.promises.readFile(filePath, 'utf8')
  );
  
  if (readError) {
    return { error: `Failed to read file: ${readError.message}` };
  }
  
  const [parseError, config] = attempt(() => JSON.parse(content));
  
  if (parseError) {
    return { error: `Invalid JSON in config file: ${parseError.message}` };
  }
  
  return { config };
}

// Usage in pipeline
async function userRegistrationPipeline(registrationData: any) {
  // Step 1: Validate input
  const [validationError] = attempt(() => {
    invariant(registrationData.email, 'Email is required');
    invariant(registrationData.password, 'Password is required');
    invariant(registrationData.password.length >= 8, 'Password must be at least 8 characters');
  });
  
  if (validationError) {
    return { step: 'validation', error: validationError.message };
  }
  
  // Step 2: Check if user exists
  const [checkError, existingUser] = await attemptAsync(() => 
    database.users.findByEmail(registrationData.email)
  );
  
  if (checkError) {
    return { step: 'user-check', error: checkError.message };
  }
  
  if (existingUser) {
    return { step: 'user-check', error: 'User already exists' };
  }
  
  // Step 3: Hash password
  const [hashError, hashedPassword] = await attemptAsync(() => 
    bcrypt.hash(registrationData.password, 10)
  );
  
  if (hashError) {
    return { step: 'password-hash', error: hashError.message };
  }
  
  // Step 4: Create user
  const [createError, user] = await attemptAsync(() => 
    database.users.create({
      ...registrationData,
      password: hashedPassword
    })
  );
  
  if (createError) {
    return { step: 'user-creation', error: createError.message };
  }
  
  return { success: true, user };
}

Advanced Error Handling Patterns

Result Type Pattern

import { attempt, attemptAsync } from 'es-toolkit/error';

type Result<T, E = Error> = {
  success: true;
  data: T;
} | {
  success: false;
  error: E;
};

function createResult<T, E = Error>(
  attemptResult: [E, null] | [null, T]
): Result<T, E> {
  const [error, data] = attemptResult;
  
  if (error) {
    return { success: false, error };
  }
  
  return { success: true, data };
}

async function safeApiCall(url: string): Promise<Result<any>> {
  const result = await attemptAsync(async () => {
    const response = await fetch(url);
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    }
    return response.json();
  });
  
  return createResult(result);
}

// Usage
async function handleApiCall() {
  const result = await safeApiCall('/api/data');
  
  if (result.success) {
    console.log('Data:', result.data);
  } else {
    console.error('Error:', result.error.message);
  }
}

Error Aggregation

import { attempt, invariant } from 'es-toolkit/error';

class ValidationError extends Error {
  constructor(public errors: string[]) {
    super(`Validation failed: ${errors.join(', ')}`);
    this.name = 'ValidationError';
  }
}

function validateForm(formData: any): ValidationError | null {
  const errors: string[] = [];
  
  // Collect all validation errors
  const validations = [
    () => invariant(formData.name, 'Name is required'),
    () => invariant(formData.email, 'Email is required'),
    () => invariant(formData.email.includes('@'), 'Invalid email format'),
    () => invariant(formData.age >= 18, 'Must be at least 18 years old'),
    () => invariant(formData.password, 'Password is required'),
    () => invariant(formData.password.length >= 8, 'Password must be at least 8 characters')
  ];
  
  validations.forEach(validation => {
    const [error] = attempt(validation);
    if (error) {
      errors.push(error.message);
    }
  });
  
  return errors.length > 0 ? new ValidationError(errors) : null;
}

// Usage
function processForm(formData: any) {
  const validationError = validateForm(formData);
  
  if (validationError) {
    return {
      success: false,
      errors: validationError.errors
    };
  }
  
  return { success: true };
}

Retry with Exponential Backoff

import { attemptAsync, TimeoutError } from 'es-toolkit/error';

async function retryWithBackoff<T>(
  operation: () => Promise<T>,
  maxRetries: number = 3,
  baseDelayMs: number = 1000,
  maxDelayMs: number = 30000
): Promise<T> {
  let lastError: Error;
  
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    const [error, result] = await attemptAsync(operation);
    
    if (!error) {
      return result;
    }
    
    lastError = error;
    
    // Don't retry on certain errors
    if (error instanceof AbortError || error instanceof TimeoutError) {
      throw error;
    }
    
    if (attempt < maxRetries - 1) {
      const delay = Math.min(baseDelayMs * Math.pow(2, attempt), maxDelayMs);
      const jitter = delay * 0.1 * Math.random(); // Add jitter
      
      console.log(`Attempt ${attempt + 1} failed, retrying in ${delay + jitter}ms...`);
      await new Promise(resolve => setTimeout(resolve, delay + jitter));
    }
  }
  
  throw lastError!;
}

// Usage
async function reliableFetch(url: string) {
  return retryWithBackoff(
    async () => {
      const response = await fetch(url);
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }
      return response.json();
    },
    3,      // max retries
    1000,   // base delay: 1 second
    10000   // max delay: 10 seconds
  );
}

Circuit Breaker with Error Handling

import { attempt, attemptAsync } from 'es-toolkit/error';

class CircuitBreakerError extends Error {
  constructor(message: string) {
    super(message);
    this.name = 'CircuitBreakerError';
  }
}

class CircuitBreaker<T> {
  private failures = 0;
  private lastFailureTime = 0;
  private state: 'CLOSED' | 'OPEN' | 'HALF_OPEN' = 'CLOSED';
  
  constructor(
    private operation: () => Promise<T>,
    private failureThreshold: number = 5,
    private resetTimeoutMs: number = 60000
  ) {}
  
  async execute(): Promise<T> {
    const [stateError] = attempt(() => this.checkState());
    if (stateError) {
      throw stateError;
    }
    
    const [operationError, result] = await attemptAsync(this.operation);
    
    if (operationError) {
      this.recordFailure();
      throw operationError;
    }
    
    this.recordSuccess();
    return result;
  }
  
  private checkState(): void {
    if (this.state === 'OPEN') {
      if (Date.now() - this.lastFailureTime < this.resetTimeoutMs) {
        throw new CircuitBreakerError('Circuit breaker is OPEN');
      } else {
        this.state = 'HALF_OPEN';
      }
    }
  }
  
  private recordFailure(): void {
    this.failures++;
    this.lastFailureTime = Date.now();
    
    if (this.failures >= this.failureThreshold) {
      this.state = 'OPEN';
    }
  }
  
  private recordSuccess(): void {
    this.failures = 0;
    this.state = 'CLOSED';
  }
}

// Usage
const apiCircuitBreaker = new CircuitBreaker(
  () => fetch('/api/unstable-endpoint').then(r => r.json()),
  3,     // failure threshold
  30000  // reset timeout: 30 seconds
);

async function callApi() {
  const [error, result] = await attemptAsync(() => apiCircuitBreaker.execute());
  
  if (error) {
    if (error instanceof CircuitBreakerError) {
      console.log('Service temporarily unavailable');
      return null;
    } else {
      console.log('API call failed:', error.message);
      return null;
    }
  }
  
  return result;
}

Error Logging and Monitoring

import { attempt, attemptAsync } from 'es-toolkit/error';

interface ErrorContext {
  operation: string;
  userId?: string;
  requestId?: string;
  timestamp: number;
  metadata?: Record<string, any>;
}

class ErrorLogger {
  static log(error: Error, context: ErrorContext): void {
    const logEntry = {
      error: {
        name: error.name,
        message: error.message,
        stack: error.stack
      },
      context,
      severity: this.getSeverity(error)
    };
    
    console.error('Application Error:', JSON.stringify(logEntry, null, 2));
    
    // Send to monitoring service
    this.sendToMonitoring(logEntry);
  }
  
  private static getSeverity(error: Error): 'low' | 'medium' | 'high' | 'critical' {
    if (error instanceof AbortError) return 'low';
    if (error instanceof TimeoutError) return 'medium';
    if (error.name === 'ValidationError') return 'low';
    if (error.message.includes('database')) return 'high';
    return 'medium';
  }
  
  private static sendToMonitoring(logEntry: any): void {
    // Implementation for sending to monitoring service
    // This would typically be async but we don't want to block
    setImmediate(() => {
      attempt(() => {
        // Send to monitoring service
        console.log('Sent to monitoring:', logEntry);
      });
    });
  }
}

// Wrapper function for operations with automatic error logging
async function withErrorLogging<T>(
  operation: () => Promise<T>,
  context: Omit<ErrorContext, 'timestamp'>
): Promise<T | null> {
  const [error, result] = await attemptAsync(operation);
  
  if (error) {
    ErrorLogger.log(error, {
      ...context,
      timestamp: Date.now()
    });
    return null;
  }
  
  return result;
}

// Usage
async function userService(userId: string, requestId: string) {
  const user = await withErrorLogging(
    () => database.users.findById(userId),
    {
      operation: 'fetch-user',
      userId,
      requestId
    }
  );
  
  if (!user) {
    return null;
  }
  
  const profile = await withErrorLogging(
    () => database.profiles.findByUserId(userId),
    {
      operation: 'fetch-user-profile',
      userId,
      requestId,
      metadata: { userEmail: user.email }
    }
  );
  
  return { user, profile };
}

Error Handling Best Practices

Consistent Error Types

import { invariant } from 'es-toolkit/error';

// Define domain-specific error types
class ValidationError extends Error {
  constructor(field: string, message: string) {
    super(`Validation error for ${field}: ${message}`);
    this.name = 'ValidationError';
  }
}

class BusinessLogicError extends Error {
  constructor(message: string) {
    super(message);
    this.name = 'BusinessLogicError';
  }
}

class ExternalServiceError extends Error {
  constructor(service: string, message: string) {
    super(`External service error (${service}): ${message}`);
    this.name = 'ExternalServiceError';
  }
}

// Use consistent error handling patterns
function validateUser(userData: any): void {
  invariant(userData.email, new ValidationError('email', 'Email is required'));
  invariant(userData.age >= 18, new BusinessLogicError('User must be at least 18 years old'));
}

Performance Considerations

  • Error Object Creation: Creating Error objects is expensive; avoid in hot paths
  • Stack Trace: Error stack traces consume memory; consider disabling in production for non-critical errors
  • Error Logging: Batch error logs to avoid I/O bottlenecks
  • Circuit Breaker: Use circuit breakers to prevent cascading failures
  • Graceful Degradation: Design systems to continue operating with reduced functionality during errors