javascript

examples

examples.js
/**
 * Advanced Error Handling - Examples
 * Comprehensive patterns for async error handling
 */

// =============================================================================
// 1. BASIC TRY-CATCH WITH ASYNC/AWAIT
// =============================================================================

console.log('--- Basic Try-Catch ---');

// Simulated async operations
function simulateFetch(url) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (url.includes('fail')) {
        reject(new Error(`Failed to fetch: ${url}`));
      } else {
        resolve({ data: 'Success', url });
      }
    }, 100);
  });
}

async function basicTryCatch() {
  try {
    const result = await simulateFetch('/api/data');
    console.log('Success:', result);
  } catch (error) {
    console.log('Caught error:', error.message);
  } finally {
    console.log('Cleanup: always runs');
  }
}

// Run example
basicTryCatch();

// =============================================================================
// 2. CUSTOM ERROR CLASSES
// =============================================================================

console.log('\n--- Custom Error Classes ---');

class NetworkError extends Error {
  constructor(message, statusCode, url) {
    super(message);
    this.name = 'NetworkError';
    this.statusCode = statusCode;
    this.url = url;
    this.timestamp = new Date();
  }

  toJSON() {
    return {
      name: this.name,
      message: this.message,
      statusCode: this.statusCode,
      url: this.url,
      timestamp: this.timestamp,
    };
  }
}

class ValidationError extends Error {
  constructor(field, value, constraint) {
    super(`Validation failed for ${field}: ${constraint}`);
    this.name = 'ValidationError';
    this.field = field;
    this.value = value;
    this.constraint = constraint;
  }
}

class TimeoutError extends Error {
  constructor(operation, duration) {
    super(`Operation "${operation}" timed out after ${duration}ms`);
    this.name = 'TimeoutError';
    this.operation = operation;
    this.duration = duration;
  }
}

class RetryableError extends Error {
  constructor(message, retryAfter = 1000) {
    super(message);
    this.name = 'RetryableError';
    this.retryAfter = retryAfter;
    this.retryable = true;
  }
}

// Using custom errors
function fetchWithCustomError(url) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (url.includes('404')) {
        reject(new NetworkError('Not Found', 404, url));
      } else if (url.includes('429')) {
        reject(new RetryableError('Rate limited', 2000));
      } else if (url.includes('validation')) {
        reject(new ValidationError('email', 'invalid@', 'must be valid email'));
      } else {
        resolve({ data: 'OK' });
      }
    }, 100);
  });
}

async function handleCustomErrors() {
  const urls = ['/api/404', '/api/429', '/api/validation'];

  for (const url of urls) {
    try {
      await fetchWithCustomError(url);
    } catch (error) {
      if (error instanceof NetworkError) {
        console.log(`NetworkError: ${error.statusCode} at ${error.url}`);
      } else if (error instanceof RetryableError) {
        console.log(`RetryableError: retry after ${error.retryAfter}ms`);
      } else if (error instanceof ValidationError) {
        console.log(`ValidationError: ${error.field} - ${error.constraint}`);
      } else {
        console.log('Unknown error:', error.message);
      }
    }
  }
}

setTimeout(handleCustomErrors, 200);

// =============================================================================
// 3. ERROR RECOVERY PATTERNS
// =============================================================================

console.log('\n--- Error Recovery Patterns ---');

// Fallback pattern
async function fetchWithFallback(primary, fallback) {
  try {
    return await simulateFetch(primary);
  } catch (primaryError) {
    console.log(`Primary failed: ${primaryError.message}, trying fallback...`);
    return await simulateFetch(fallback);
  }
}

// Default value pattern
async function fetchWithDefault(url, defaultValue) {
  try {
    return await simulateFetch(url);
  } catch {
    console.log('Using default value');
    return defaultValue;
  }
}

// Run recovery examples
setTimeout(async () => {
  console.log('\n--- Fallback Pattern ---');
  const result = await fetchWithFallback('/api/fail', '/api/success');
  console.log('Final result:', result);

  console.log('\n--- Default Value Pattern ---');
  const result2 = await fetchWithDefault('/api/fail', { data: 'default' });
  console.log('Result with default:', result2);
}, 500);

// =============================================================================
// 4. RETRY PATTERN
// =============================================================================

console.log('\n--- Retry Pattern ---');

async function withRetry(fn, maxRetries = 3, delay = 100, backoff = 2) {
  let lastError;

  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      lastError = error;
      console.log(`Attempt ${attempt} failed: ${error.message}`);

      if (attempt < maxRetries) {
        const waitTime = delay * Math.pow(backoff, attempt - 1);
        console.log(`Waiting ${waitTime}ms before retry...`);
        await new Promise((resolve) => setTimeout(resolve, waitTime));
      }
    }
  }

  throw new Error(
    `All ${maxRetries} attempts failed. Last error: ${lastError.message}`
  );
}

// Retry with conditional
async function withConditionalRetry(fn, shouldRetry, maxRetries = 3) {
  let lastError;

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

      if (!shouldRetry(error) || attempt === maxRetries) {
        throw error;
      }

      console.log(`Retrying due to: ${error.message}`);
      await new Promise((r) => setTimeout(r, 100));
    }
  }

  throw lastError;
}

// Demonstrate retry
let attempts = 0;
const flakeyOperation = () =>
  new Promise((resolve, reject) => {
    attempts++;
    if (attempts < 3) {
      reject(new Error(`Attempt ${attempts} failed`));
    } else {
      resolve('Success on attempt ' + attempts);
    }
  });

setTimeout(async () => {
  console.log('\n--- Retry Demonstration ---');
  try {
    const result = await withRetry(flakeyOperation, 5, 50, 1);
    console.log('Final result:', result);
  } catch (error) {
    console.log('All retries failed:', error.message);
  }
}, 1000);

// =============================================================================
// 5. MULTIPLE ERROR HANDLING LEVELS
// =============================================================================

console.log('\n--- Multiple Error Levels ---');

class DataService {
  // Low-level: specific error handling
  async fetchRaw(url) {
    try {
      const response = await simulateFetch(url);
      return response;
    } catch (error) {
      // Transform to domain error
      throw new NetworkError(
        `Failed to fetch data: ${error.message}`,
        500,
        url
      );
    }
  }

  // Mid-level: business logic errors
  async getUser(id) {
    try {
      const data = await this.fetchRaw(`/users/${id}`);

      if (!data.data) {
        throw new ValidationError('user', id, 'must exist');
      }

      return data;
    } catch (error) {
      if (error instanceof NetworkError) {
        // Let network errors bubble up
        throw error;
      }
      // Wrap other errors
      throw new Error(`User retrieval failed: ${error.message}`);
    }
  }

  // High-level: user-facing error handling
  async getUserSafely(id) {
    try {
      return await this.getUser(id);
    } catch (error) {
      console.log('Service error:', error.constructor.name, '-', error.message);
      return null; // Return safe default
    }
  }
}

const service = new DataService();
setTimeout(async () => {
  console.log('\n--- Service Error Handling ---');
  const user = await service.getUserSafely(123);
  console.log('User result:', user);
}, 1500);

// =============================================================================
// 6. AGGREGATE ERROR HANDLING
// =============================================================================

console.log('\n--- Aggregate Error Handling ---');

async function collectErrors(operations) {
  const errors = [];
  const results = [];

  for (const op of operations) {
    try {
      results.push(await op());
    } catch (error) {
      errors.push(error);
    }
  }

  return { results, errors };
}

// Handle all results with Promise.allSettled
async function handleAllResults(promises) {
  const results = await Promise.allSettled(promises);

  const successful = results
    .filter((r) => r.status === 'fulfilled')
    .map((r) => r.value);

  const failed = results
    .filter((r) => r.status === 'rejected')
    .map((r) => r.reason);

  return {
    successful,
    failed,
    hasErrors: failed.length > 0,
    allSucceeded: failed.length === 0,
  };
}

setTimeout(async () => {
  console.log('\n--- Aggregate Results ---');
  const operations = [
    simulateFetch('/api/1'),
    simulateFetch('/api/fail'),
    simulateFetch('/api/3'),
  ];

  const { successful, failed, hasErrors } = await handleAllResults(operations);
  console.log(`Successful: ${successful.length}, Failed: ${failed.length}`);
  console.log('Has errors:', hasErrors);
}, 2000);

// =============================================================================
// 7. ERROR WRAPPING AND CONTEXT
// =============================================================================

console.log('\n--- Error Wrapping ---');

class ContextualError extends Error {
  constructor(message, cause, context = {}) {
    super(message);
    this.name = 'ContextualError';
    this.cause = cause;
    this.context = context;
    this.timestamp = new Date();
  }

  getFullStack() {
    let stack = this.stack;
    if (this.cause instanceof Error) {
      stack += '\nCaused by: ' + this.cause.stack;
    }
    return stack;
  }
}

function wrapError(error, message, context = {}) {
  return new ContextualError(message, error, {
    ...context,
    originalMessage: error.message,
  });
}

async function processWithContext() {
  try {
    try {
      await simulateFetch('/api/fail');
    } catch (lowLevelError) {
      throw wrapError(lowLevelError, 'Failed to process user data', {
        userId: 123,
        action: 'fetch',
      });
    }
  } catch (error) {
    console.log('Error message:', error.message);
    console.log('Original cause:', error.cause?.message);
    console.log('Context:', error.context);
  }
}

setTimeout(processWithContext, 2500);

// =============================================================================
// 8. ASYNC ERROR BOUNDARY
// =============================================================================

console.log('\n--- Error Boundary ---');

function createErrorBoundary(handler) {
  return function (target, key, descriptor) {
    const original = descriptor.value;

    descriptor.value = async function (...args) {
      try {
        return await original.apply(this, args);
      } catch (error) {
        return handler(error, { method: key, args });
      }
    };

    return descriptor;
  };
}

// Manual error boundary wrapper
function withErrorBoundary(fn, errorHandler) {
  return async (...args) => {
    try {
      return await fn(...args);
    } catch (error) {
      return errorHandler(error, args);
    }
  };
}

const safeFetch = withErrorBoundary(
  async (url) => {
    const result = await simulateFetch(url);
    return result;
  },
  (error, args) => {
    console.log(`Error boundary caught: ${error.message} for ${args[0]}`);
    return { error: true, message: error.message };
  }
);

setTimeout(async () => {
  console.log('\n--- Error Boundary Demo ---');
  const result1 = await safeFetch('/api/success');
  console.log('Result 1:', result1);

  const result2 = await safeFetch('/api/fail');
  console.log('Result 2:', result2);
}, 3000);

// =============================================================================
// 9. CLEANUP AND RESOURCE MANAGEMENT
// =============================================================================

console.log('\n--- Resource Cleanup ---');

class ResourceManager {
  constructor() {
    this.resources = [];
  }

  async acquire(name) {
    console.log(`Acquiring: ${name}`);
    this.resources.push(name);
    return { name, acquired: true };
  }

  async release(name) {
    console.log(`Releasing: ${name}`);
    this.resources = this.resources.filter((r) => r !== name);
  }

  async releaseAll() {
    console.log('Releasing all resources...');
    for (const resource of [...this.resources]) {
      await this.release(resource);
    }
  }
}

async function withResources(fn) {
  const manager = new ResourceManager();

  try {
    return await fn(manager);
  } catch (error) {
    console.log('Error during operation:', error.message);
    throw error;
  } finally {
    await manager.releaseAll();
  }
}

setTimeout(async () => {
  console.log('\n--- Resource Management Demo ---');
  try {
    await withResources(async (manager) => {
      await manager.acquire('database');
      await manager.acquire('cache');
      // Simulate error
      throw new Error('Operation failed');
    });
  } catch (error) {
    console.log('Handled:', error.message);
  }
}, 3500);

// =============================================================================
// 10. CIRCUIT BREAKER PATTERN
// =============================================================================

console.log('\n--- Circuit Breaker ---');

class CircuitBreaker {
  constructor(threshold = 3, timeout = 5000) {
    this.failures = 0;
    this.threshold = threshold;
    this.timeout = timeout;
    this.state = 'CLOSED'; // CLOSED, OPEN, HALF-OPEN
    this.nextAttempt = null;
  }

  async execute(fn) {
    if (this.state === 'OPEN') {
      if (Date.now() > this.nextAttempt) {
        this.state = 'HALF-OPEN';
        console.log('Circuit breaker: HALF-OPEN');
      } else {
        throw new Error('Circuit breaker is OPEN');
      }
    }

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

  onSuccess() {
    this.failures = 0;
    if (this.state === 'HALF-OPEN') {
      this.state = 'CLOSED';
      console.log('Circuit breaker: CLOSED (recovered)');
    }
  }

  onFailure() {
    this.failures++;
    console.log(`Circuit breaker: failure ${this.failures}/${this.threshold}`);

    if (this.failures >= this.threshold) {
      this.state = 'OPEN';
      this.nextAttempt = Date.now() + this.timeout;
      console.log(
        `Circuit breaker: OPEN (will retry at ${new Date(
          this.nextAttempt
        ).toISOString()})`
      );
    }
  }
}

const breaker = new CircuitBreaker(2, 1000);
const failingOp = () => simulateFetch('/api/fail');

setTimeout(async () => {
  console.log('\n--- Circuit Breaker Demo ---');

  for (let i = 0; i < 4; i++) {
    try {
      await breaker.execute(failingOp);
    } catch (error) {
      console.log('Error:', error.message);
    }
    await new Promise((r) => setTimeout(r, 100));
  }
}, 4000);
Examples - JavaScript Tutorial | DeepML