javascript

examples

examples.js
/**
 * ========================================
 * 9.4 ERROR HANDLING IN ASYNC CODE - EXAMPLES
 * ========================================
 */

// Helper functions
function delay(ms, value) {
  return new Promise((resolve) => setTimeout(() => resolve(value), ms));
}

function delayReject(ms, message) {
  return new Promise((_, reject) =>
    setTimeout(() => reject(new Error(message)), ms)
  );
}

/**
 * EXAMPLE 1: Basic Promise Error Handling
 */
console.log('--- Example 1: Promise .catch() ---');

Promise.resolve('start')
  .then((value) => {
    console.log('Step 1:', value);
    throw new Error('Step 1 failed!');
  })
  .then((value) => {
    console.log('Step 2:', value); // Never runs
  })
  .catch((error) => {
    console.log('Caught:', error.message);
  });

/**
 * EXAMPLE 2: Error Recovery in Promise Chain
 */
console.log('\n--- Example 2: Error Recovery ---');

function fetchPrimary() {
  return delayReject(50, 'Primary server down');
}

function fetchBackup() {
  return delay(50, { source: 'backup', data: [1, 2, 3] });
}

fetchPrimary()
  .catch((err) => {
    console.log('Primary failed:', err.message);
    console.log('Trying backup...');
    return fetchBackup();
  })
  .then((data) => {
    console.log('Data received from:', data.source);
  });

/**
 * EXAMPLE 3: try/catch with async/await
 */
console.log('\n--- Example 3: try/catch ---');

async function fetchWithErrorHandling() {
  try {
    const data = await delayReject(50, 'Network error');
    return data;
  } catch (error) {
    console.log('Error caught:', error.message);
    return { fallback: true };
  }
}

fetchWithErrorHandling().then((result) => console.log('Result:', result));

/**
 * EXAMPLE 4: Multiple try/catch Blocks
 */
console.log('\n--- Example 4: Multiple try/catch ---');

async function multiStepProcess() {
  let step1Result;

  // Step 1
  try {
    step1Result = await delay(50, { value: 10 });
    console.log('Step 1 succeeded');
  } catch (error) {
    console.log('Step 1 failed, using default');
    step1Result = { value: 0 };
  }

  // Step 2
  try {
    if (step1Result.value === 0) {
      throw new Error('Cannot process zero value');
    }
    const step2Result = await delay(50, step1Result.value * 2);
    console.log('Step 2 result:', step2Result);
    return step2Result;
  } catch (error) {
    console.log('Step 2 failed:', error.message);
    return null;
  }
}

multiStepProcess();

/**
 * EXAMPLE 5: Error Tuple Pattern
 */
console.log('\n--- Example 5: Error Tuple Pattern ---');

async function safeAwait(promise) {
  try {
    const result = await promise;
    return [null, result];
  } catch (error) {
    return [error, null];
  }
}

async function useTuplePattern() {
  // Success case
  const [err1, data1] = await safeAwait(delay(50, 'success'));
  if (err1) {
    console.log('Error:', err1.message);
  } else {
    console.log('Data:', data1);
  }

  // Error case
  const [err2, data2] = await safeAwait(delayReject(50, 'failed'));
  if (err2) {
    console.log('Error:', err2.message);
  } else {
    console.log('Data:', data2);
  }
}

useTuplePattern();

/**
 * EXAMPLE 6: Result Object Pattern
 */
console.log('\n--- Example 6: Result Object ---');

async function fetchResult(shouldFail) {
  try {
    if (shouldFail) {
      throw new Error('Simulated failure');
    }
    const data = await delay(50, { items: [1, 2, 3] });
    return { success: true, data, error: null };
  } catch (error) {
    return { success: false, data: null, error };
  }
}

async function useResultPattern() {
  const result1 = await fetchResult(false);
  if (result1.success) {
    console.log('Success:', result1.data);
  }

  const result2 = await fetchResult(true);
  if (!result2.success) {
    console.log('Failed:', result2.error.message);
  }
}

useResultPattern();

/**
 * EXAMPLE 7: Custom Error Types
 */
console.log('\n--- Example 7: Custom Error Types ---');

class NetworkError extends Error {
  constructor(message, statusCode) {
    super(message);
    this.name = 'NetworkError';
    this.statusCode = statusCode;
    this.retryable = statusCode >= 500;
  }
}

class ValidationError extends Error {
  constructor(message, field) {
    super(message);
    this.name = 'ValidationError';
    this.field = field;
  }
}

async function simulateRequest(type) {
  await delay(50);
  switch (type) {
    case 'network':
      throw new NetworkError('Server unavailable', 503);
    case 'validation':
      throw new ValidationError('Invalid email', 'email');
    case 'success':
      return { data: 'ok' };
    default:
      throw new Error('Unknown error');
  }
}

async function handleTypedErrors() {
  for (const type of ['success', 'network', 'validation', 'unknown']) {
    try {
      const result = await simulateRequest(type);
      console.log(`${type}: Success`, result);
    } catch (error) {
      if (error instanceof NetworkError) {
        console.log(
          `${type}: Network error (${error.statusCode}), retryable: ${error.retryable}`
        );
      } else if (error instanceof ValidationError) {
        console.log(`${type}: Validation error on field '${error.field}'`);
      } else {
        console.log(`${type}: Unexpected error:`, error.message);
      }
    }
  }
}

handleTypedErrors();

/**
 * EXAMPLE 8: Promise.all Error Behavior
 */
console.log('\n--- Example 8: Promise.all Fails Fast ---');

async function demonstratePromiseAll() {
  try {
    const results = await Promise.all([
      delay(100, 'slow success'),
      delayReject(50, 'fast failure'),
      delay(75, 'medium success'),
    ]);
    console.log('All succeeded:', results);
  } catch (error) {
    console.log('Promise.all failed:', error.message);
    console.log('(Other promises still running but results discarded)');
  }
}

demonstratePromiseAll();

/**
 * EXAMPLE 9: Promise.allSettled for Graceful Handling
 */
console.log('\n--- Example 9: Promise.allSettled ---');

async function demonstrateAllSettled() {
  const results = await Promise.allSettled([
    delay(50, 'success 1'),
    delayReject(75, 'failure'),
    delay(100, 'success 2'),
  ]);

  results.forEach((result, i) => {
    if (result.status === 'fulfilled') {
      console.log(`Promise ${i}: fulfilled with`, result.value);
    } else {
      console.log(`Promise ${i}: rejected with`, result.reason.message);
    }
  });

  // Separate successes and failures
  const successes = results
    .filter((r) => r.status === 'fulfilled')
    .map((r) => r.value);
  const failures = results
    .filter((r) => r.status === 'rejected')
    .map((r) => r.reason);

  console.log(`${successes.length} succeeded, ${failures.length} failed`);
}

demonstrateAllSettled();

/**
 * EXAMPLE 10: Retry with Error Handling
 */
console.log('\n--- Example 10: Retry Pattern ---');

async function retryWithBackoff(fn, maxRetries, baseDelay = 100) {
  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 delayMs = baseDelay * Math.pow(2, attempt - 1);
        console.log(`Retrying in ${delayMs}ms...`);
        await delay(delayMs);
      }
    }
  }

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

let attemptNum = 0;
async function flakyOperation() {
  attemptNum++;
  if (attemptNum < 3) {
    throw new Error(`Attempt ${attemptNum} failed`);
  }
  return 'Success on attempt ' + attemptNum;
}

retryWithBackoff(flakyOperation, 5, 50)
  .then((result) => console.log('Final result:', result))
  .catch((error) => console.log('Final error:', error.message));

/**
 * EXAMPLE 11: Error Aggregation
 */
console.log('\n--- Example 11: Error Aggregation ---');

class AggregateError extends Error {
  constructor(errors, message = 'Multiple errors occurred') {
    super(message);
    this.name = 'AggregateError';
    this.errors = errors;
  }
}

async function runWithErrorCollection(tasks) {
  const results = [];
  const errors = [];

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

  if (errors.length > 0) {
    console.log(`Completed with ${errors.length} errors:`);
    errors.forEach((e, i) => console.log(`  ${i + 1}. ${e.message}`));
  }

  return { results, errors };
}

runWithErrorCollection([
  () => delay(50, 'Task 1 OK'),
  () => delayReject(50, 'Task 2 Failed'),
  () => delay(50, 'Task 3 OK'),
  () => delayReject(50, 'Task 4 Failed'),
]).then(({ results, errors }) => {
  console.log('Successful results:', results);
});

/**
 * EXAMPLE 12: finally for Cleanup
 */
console.log('\n--- Example 12: finally Cleanup ---');

async function withCleanup(shouldFail) {
  console.log('Acquiring resource...');
  const resource = { id: Date.now(), active: true };

  try {
    if (shouldFail) {
      throw new Error('Operation failed');
    }
    await delay(50);
    console.log('Operation succeeded');
    return 'success';
  } catch (error) {
    console.log('Operation failed:', error.message);
    throw error;
  } finally {
    // Always runs
    console.log('Releasing resource...');
    resource.active = false;
  }
}

// Success case
withCleanup(false).then((r) => console.log('Result:', r));

// Failure case
delay(200).then(() => {
  withCleanup(true).catch((e) => console.log('Caught in caller:', e.message));
});

/**
 * EXAMPLE 13: Resource Manager with Cleanup
 */
console.log('\n--- Example 13: Resource Manager ---');

class Connection {
  constructor(id) {
    this.id = id;
    this.open = true;
  }

  async query(sql) {
    if (!this.open) throw new Error('Connection closed');
    await delay(50);
    return [`Result for: ${sql}`];
  }

  async close() {
    this.open = false;
    console.log(`Connection ${this.id} closed`);
  }
}

class ConnectionPool {
  static nextId = 0;

  static async getConnection() {
    const conn = new Connection(++this.nextId);
    console.log(`Connection ${conn.id} opened`);
    return conn;
  }

  static async use(asyncFn) {
    const connection = await this.getConnection();
    try {
      return await asyncFn(connection);
    } finally {
      await connection.close();
    }
  }
}

async function demonstrateResourceManager() {
  // Connection always closed, even on error
  try {
    const result = await ConnectionPool.use(async (conn) => {
      const data = await conn.query('SELECT * FROM users');
      // throw new Error("Processing error");  // Uncomment to test cleanup on error
      return data;
    });
    console.log('Query result:', result);
  } catch (error) {
    console.log('Error handled:', error.message);
  }
}

demonstrateResourceManager();

/**
 * EXAMPLE 14: Error Context Enhancement
 */
console.log('\n--- Example 14: Error Context ---');

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

  toJSON() {
    return {
      message: this.message,
      context: this.context,
      timestamp: this.timestamp,
      stack: this.stack,
    };
  }
}

async function fetchUser(userId) {
  try {
    await delay(50);
    throw new Error('Database connection failed');
  } catch (error) {
    throw new ContextualError(`Failed to fetch user ${userId}`, {
      userId,
      operation: 'fetchUser',
      originalError: error.message,
    });
  }
}

async function demonstrateContext() {
  try {
    await fetchUser(123);
  } catch (error) {
    console.log('Error with context:', JSON.stringify(error.toJSON(), null, 2));
  }
}

delay(400).then(() => demonstrateContext());

/**
 * EXAMPLE 15: Circuit Breaker Pattern
 */
console.log('\n--- Example 15: Circuit Breaker ---');

class CircuitBreaker {
  constructor(options = {}) {
    this.failureThreshold = options.failureThreshold || 3;
    this.resetTimeout = options.resetTimeout || 1000;
    this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
    this.failures = 0;
    this.lastFailure = null;
  }

  async call(asyncFn) {
    if (this.state === 'OPEN') {
      if (Date.now() - this.lastFailure > this.resetTimeout) {
        this.state = 'HALF_OPEN';
        console.log('Circuit: HALF_OPEN (testing)');
      } else {
        throw new Error('Circuit breaker is OPEN');
      }
    }

    try {
      const result = await asyncFn();
      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: CLOSED (recovered)');
    }
  }

  onFailure() {
    this.failures++;
    this.lastFailure = Date.now();

    if (this.failures >= this.failureThreshold) {
      this.state = 'OPEN';
      console.log('Circuit: OPEN (too many failures)');
    }
  }
}

async function demonstrateCircuitBreaker() {
  const breaker = new CircuitBreaker({
    failureThreshold: 2,
    resetTimeout: 200,
  });

  const unreliableService = async () => {
    await delay(20);
    if (Math.random() < 0.8) throw new Error('Service error');
    return 'Success';
  };

  for (let i = 0; i < 6; i++) {
    try {
      const result = await breaker.call(unreliableService);
      console.log(`Call ${i + 1}: ${result}`);
    } catch (error) {
      console.log(`Call ${i + 1}: ${error.message}`);
    }
    await delay(100);
  }
}

delay(600).then(() => demonstrateCircuitBreaker());

/**
 * EXAMPLE 16: Global Unhandled Rejection Handler
 */
console.log('\n--- Example 16: Global Handler (Node.js) ---');

// In Node.js environment:
if (typeof process !== 'undefined') {
  process.on('unhandledRejection', (reason, promise) => {
    console.log('Unhandled rejection detected!');
    console.log('Reason:', reason);
    // In production: log to monitoring service
  });
}

// Intentionally unhandled (for demonstration)
// Promise.reject(new Error("Unhandled!"));

console.log('Global handler registered');

/**
 * EXAMPLE 17: Error Handling Middleware Pattern
 */
console.log('\n--- Example 17: Error Middleware ---');

class AsyncPipeline {
  constructor() {
    this.middleware = [];
    this.errorHandlers = [];
  }

  use(fn) {
    this.middleware.push(fn);
    return this;
  }

  onError(handler) {
    this.errorHandlers.push(handler);
    return this;
  }

  async execute(context) {
    try {
      for (const fn of this.middleware) {
        context = await fn(context);
      }
      return context;
    } catch (error) {
      for (const handler of this.errorHandlers) {
        try {
          return await handler(error, context);
        } catch (e) {
          // Handler failed, try next
          continue;
        }
      }
      throw error; // No handler succeeded
    }
  }
}

async function demonstratePipeline() {
  const pipeline = new AsyncPipeline()
    .use(async (ctx) => {
      await delay(20);
      return { ...ctx, step1: true };
    })
    .use(async (ctx) => {
      if (ctx.shouldFail) throw new Error('Pipeline error');
      return { ...ctx, step2: true };
    })
    .onError(async (error, ctx) => {
      console.log('Error handler caught:', error.message);
      return { ...ctx, recovered: true };
    });

  // Success
  const result1 = await pipeline.execute({ shouldFail: false });
  console.log('Pipeline success:', result1);

  // With error recovery
  const result2 = await pipeline.execute({ shouldFail: true });
  console.log('Pipeline recovered:', result2);
}

delay(1200).then(() => demonstratePipeline());

/**
 * EXAMPLE 18: Timeout with Cleanup
 */
console.log('\n--- Example 18: Timeout with Cleanup ---');

async function withTimeoutAndCleanup(asyncFn, timeoutMs, cleanup) {
  let timeoutId;
  let completed = false;

  const timeoutPromise = new Promise((_, reject) => {
    timeoutId = setTimeout(() => {
      if (!completed) {
        reject(new Error(`Timeout after ${timeoutMs}ms`));
      }
    }, timeoutMs);
  });

  try {
    const result = await Promise.race([asyncFn(), timeoutPromise]);
    completed = true;
    return result;
  } catch (error) {
    if (cleanup) await cleanup();
    throw error;
  } finally {
    clearTimeout(timeoutId);
  }
}

async function demonstrateTimeoutCleanup() {
  try {
    await withTimeoutAndCleanup(
      () => delay(100, 'result'),
      50,
      () => console.log('Cleanup after timeout')
    );
  } catch (error) {
    console.log('Caught:', error.message);
  }
}

delay(1400).then(() => demonstrateTimeoutCleanup());

/**
 * EXAMPLE 19: Async Error Boundary
 */
console.log('\n--- Example 19: Error Boundary ---');

function createErrorBoundary(options = {}) {
  const {
    onError = console.error,
    fallback = null,
    retry = false,
    maxRetries = 3,
  } = options;

  return async function (asyncFn) {
    let attempts = 0;

    while (attempts < (retry ? maxRetries : 1)) {
      try {
        attempts++;
        return await asyncFn();
      } catch (error) {
        onError(error, attempts);

        if (!retry || attempts >= maxRetries) {
          return typeof fallback === 'function' ? fallback(error) : fallback;
        }

        await delay(100 * attempts); // Backoff
      }
    }
  };
}

async function demonstrateErrorBoundary() {
  const safeFetch = createErrorBoundary({
    onError: (err, attempt) =>
      console.log(`Attempt ${attempt} failed:`, err.message),
    fallback: { data: 'default' },
    retry: true,
    maxRetries: 2,
  });

  const result = await safeFetch(async () => {
    throw new Error('Always fails');
  });

  console.log('Boundary result:', result);
}

delay(1600).then(() => demonstrateErrorBoundary());

/**
 * EXAMPLE 20: Complete Error Handling Strategy
 */
console.log('\n--- Example 20: Complete Strategy ---');

// Error types
class AppError extends Error {
  constructor(message, code, recoverable = false) {
    super(message);
    this.code = code;
    this.recoverable = recoverable;
  }
}

// Error logger
const errorLogger = {
  log(error, context) {
    console.log(`[ERROR] ${error.code || 'UNKNOWN'}: ${error.message}`);
    if (context) console.log(`[CONTEXT] ${JSON.stringify(context)}`);
  },
};

// API service with comprehensive error handling
class APIService {
  constructor(baseUrl) {
    this.baseUrl = baseUrl;
    this.retryConfig = { maxRetries: 3, baseDelay: 100 };
  }

  async request(endpoint, options = {}) {
    const { retries = this.retryConfig.maxRetries } = options;

    for (let attempt = 1; attempt <= retries; attempt++) {
      try {
        return await this.#makeRequest(endpoint);
      } catch (error) {
        const isLastAttempt = attempt === retries;

        // Log error
        errorLogger.log(error, { endpoint, attempt, isLastAttempt });

        // Don't retry non-recoverable errors
        if (error instanceof AppError && !error.recoverable) {
          throw error;
        }

        if (isLastAttempt) {
          throw new AppError(
            `Request failed after ${retries} attempts`,
            'MAX_RETRIES_EXCEEDED'
          );
        }

        // Wait before retry
        await delay(this.retryConfig.baseDelay * attempt);
      }
    }
  }

  async #makeRequest(endpoint) {
    await delay(50);

    // Simulate various errors
    const rand = Math.random();
    if (rand < 0.3) {
      throw new AppError('Network error', 'NETWORK_ERROR', true);
    } else if (rand < 0.4) {
      throw new AppError('Unauthorized', 'AUTH_ERROR', false);
    }

    return { data: `Response from ${endpoint}` };
  }

  async safeRequest(endpoint) {
    try {
      return await this.request(endpoint);
    } catch (error) {
      if (error.code === 'AUTH_ERROR') {
        return { error: 'Please login again', redirect: '/login' };
      }
      return { error: error.message, fallback: true };
    }
  }
}

async function demonstrateCompleteStrategy() {
  const api = new APIService('https://api.example.com');

  // Multiple requests with graceful degradation
  const results = await Promise.allSettled([
    api.safeRequest('/users'),
    api.safeRequest('/posts'),
    api.safeRequest('/comments'),
  ]);

  console.log('\nFinal Results:');
  results.forEach((result, i) => {
    const endpoints = ['/users', '/posts', '/comments'];
    console.log(
      `${endpoints[i]}: ${JSON.stringify(
        result.value || result.reason?.message
      )}`
    );
  });
}

delay(1800).then(() => demonstrateCompleteStrategy());

console.log('\n========================================');
console.log('End of Error Handling Examples');
console.log('(Results will appear asynchronously)');
console.log('========================================');
Examples - JavaScript Tutorial | DeepML