JavaScript Error Handling (Try/Catch/Finally) Cheat Sheet

Introduction

Error handling is a critical aspect of writing robust JavaScript applications. The try/catch/finally mechanism allows developers to gracefully handle runtime errors, prevent application crashes, and provide meaningful feedback to users. This cheatsheet covers everything you need to know about error handling in JavaScript, from basic syntax to advanced patterns and best practices.

Core Concepts & Principles

Error Handling Flow

  1. Try Block: Contains code that might throw an error
  2. Catch Block: Executes if an error occurs in the try block
  3. Finally Block: Always executes, regardless of whether an error occurred

Why Error Handling Matters

  • Prevents application crashes
  • Improves user experience
  • Facilitates debugging
  • Enables graceful degradation
  • Supports better application robustness

Basic Syntax & Structure

Try/Catch Basic Structure

try {
  // Code that might throw an error
  riskyOperation();
} catch (error) {
  // Code that handles the error
  handleError(error);
}

Try/Catch/Finally Complete Structure

try {
  // Code that might throw an error
  riskyOperation();
} catch (error) {
  // Code that handles the error
  handleError(error);
} finally {
  // Code that always runs, regardless of error
  cleanup();
}

Catch Block With Error Parameter

try {
  JSON.parse(invalidJson);
} catch (error) {
  console.error('Parsing error:', error.message);
  // Access error properties like error.name, error.stack, etc.
}

Try/Catch Without Error Parameter (ES2019+)

try {
  riskyOperation();
} catch {
  // Handle error without accessing error object
  displayGenericErrorMessage();
}

Built-in Error Types

Error TypeDescriptionCommon Trigger
ErrorBase error typeManual throws
SyntaxErrorInvalid syntaxParsing invalid code
ReferenceErrorReference to undefined variableUsing undeclared variables
TypeErrorValue of wrong typeCalling non-functions
RangeErrorValue outside allowed rangeInfinite recursion
URIErrorInvalid URIMalformed decodeURI()
EvalErrorError with eval() functionRare in modern JS
AggregateErrorMultiple errors wrappedPromise.any() rejections

Creating Custom Errors

Creating Basic Custom Error

// Simple custom error
function ValidationError(message) {
  this.name = 'ValidationError';
  this.message = message || 'Validation failed';
  this.stack = (new Error()).stack;
}
ValidationError.prototype = Object.create(Error.prototype);
ValidationError.prototype.constructor = ValidationError;

// Usage
throw new ValidationError('Username is too short');

ES6 Class-Based Custom Errors

class DatabaseError extends Error {
  constructor(message, code) {
    super(message);
    this.name = 'DatabaseError';
    this.code = code;
  }
  
  logError() {
    console.error(`DB Error ${this.code}: ${this.message}`);
  }
}

// Usage
throw new DatabaseError('Connection failed', 'DB_CONN_ERR');

Custom Error Hierarchy

class AppError extends Error {
  constructor(message) {
    super(message);
    this.name = this.constructor.name;
  }
}

class NetworkError extends AppError {
  constructor(message, endpoint) {
    super(message);
    this.endpoint = endpoint;
  }
}

class AuthenticationError extends AppError {
  constructor(message, userId) {
    super(message);
    this.userId = userId;
  }
}

Error Handling Techniques

Conditional Catch Blocks

try {
  riskyOperation();
} catch (error) {
  if (error instanceof TypeError) {
    handleTypeError(error);
  } else if (error instanceof RangeError) {
    handleRangeError(error);
  } else if (error.name === 'CustomError') {
    handleCustomError(error);
  } else {
    handleGenericError(error);
  }
}

Nested Try/Catch

try {
  outerOperation();
  try {
    innerOperation();
  } catch (innerError) {
    handleInnerError(innerError);
  }
} catch (outerError) {
  handleOuterError(outerError);
}

Rethrowing Errors

try {
  riskyOperation();
} catch (error) {
  // Log the error
  console.error('An error occurred:', error);
  
  // Modify the error
  error.additionalInfo = 'Error occurred in module X';
  
  // Rethrow the error
  throw error;
}

Throwing in Finally

try {
  riskyOperation();
} catch (error) {
  console.error('Caught:', error.message);
} finally {
  // If this throws, it will override any error from the try/catch
  cleanup(); // Be careful if this can throw
}

Asynchronous Error Handling

Promises Error Handling

// Using .catch()
fetchData()
  .then(data => processData(data))
  .catch(error => handleError(error));

// Chained .catch() with different handlers
fetchData()
  .then(data => processData(data))
  .catch(NetworkError, error => handleNetworkError(error))
  .catch(DatabaseError, error => handleDatabaseError(error))
  .catch(error => handleGenericError(error));

Async/Await Error Handling

// Basic try/catch with async/await
async function getData() {
  try {
    const response = await fetch('/api/data');
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('Failed to fetch data:', error);
    throw new DataFetchError('Could not retrieve data', error);
  }
}

// Using finally with async/await
async function processData() {
  let connection;
  try {
    connection = await db.connect();
    const data = await connection.query('SELECT * FROM users');
    return transformData(data);
  } catch (error) {
    logger.error('Database error:', error);
    throw error;
  } finally {
    if (connection) await connection.close();
  }
}

Higher-Order Function for Error Handling

// Wrap async functions to catch errors
const withErrorHandling = (fn) => async (...args) => {
  try {
    return await fn(...args);
  } catch (error) {
    console.error('Operation failed:', error);
    // You can return a default value or rethrow
    throw error;
  }
};

// Usage
const safeGetUser = withErrorHandling(getUser);
safeGetUser(123).then(user => console.log(user));

Common Patterns & Best Practices

Error Object Properties

try {
  nonExistentFunction();
} catch (error) {
  // Standard properties
  console.log(error.name);     // The error type name
  console.log(error.message);  // Human-readable description
  console.log(error.stack);    // Stack trace as string
  
  // Custom properties (if using custom errors)
  console.log(error.code);     // Error code (custom)
  console.log(error.data);     // Additional data (custom)
}

Error Handling Strategy Pattern

const errorHandlers = {
  TypeError: (error) => {
    // Handle TypeError
    displayTypeError(error);
  },
  NetworkError: (error) => {
    // Handle NetworkError
    retryConnection(error.endpoint);
  },
  default: (error) => {
    // Default handler
    logGenericError(error);
  }
};

try {
  riskyOperation();
} catch (error) {
  // Choose handler based on error type
  const handler = errorHandlers[error.name] || errorHandlers.default;
  handler(error);
}

Global Error Handling

// Browser environment
window.addEventListener('error', (event) => {
  console.error('Global error:', event.error);
  showErrorNotification(event.error.message);
  // Prevent default browser error handling
  event.preventDefault();
});

// Node.js environment
process.on('uncaughtException', (error) => {
  console.error('Uncaught exception:', error);
  // Log to monitoring service
  logErrorToService(error);
  // Graceful shutdown
  process.exit(1);
});

process.on('unhandledRejection', (reason, promise) => {
  console.error('Unhandled Promise rejection:', reason);
  // Log to monitoring service
  logErrorToService(reason);
});

Common Challenges & Solutions

ChallengeSolution
Too many try/catch blocksGroup related operations; use higher-order functions
Losing the original errorAttach original error as a property of new error
Inconsistent error formatsStandardize errors through a central error factory
Missing stack tracesEnsure Error.captureStackTrace() is used in custom errors
Asynchronous errors being missedUse async/await with try/catch or ensure Promise chains have .catch()
Errors in event handlersWrap handlers with try/catch or use global error listener
Non-descriptive error messagesCreate custom error types with clear, detailed messages

Advanced Error Handling Techniques

Error Boundary Pattern (Similar to React)

function createErrorBoundary(component, fallback) {
  return function (...args) {
    try {
      return component(...args);
    } catch (error) {
      console.error('Error boundary caught:', error);
      return typeof fallback === 'function' 
        ? fallback(error) 
        : fallback;
    }
  };
}

// Usage
const riskyComponent = (props) => {
  // This might throw
  return processData(props.data);
};

const fallbackUI = (error) => `<div class="error">Something went wrong: ${error.message}</div>`;

const safeComponent = createErrorBoundary(riskyComponent, fallbackUI);

Circuit Breaker Pattern

class CircuitBreaker {
  constructor(operation, options = {}) {
    this.operation = operation;
    this.failureThreshold = options.failureThreshold || 3;
    this.resetTimeout = options.resetTimeout || 10000;
    this.state = 'CLOSED';
    this.failureCount = 0;
    this.lastFailureTime = null;
  }

  async exec(...args) {
    if (this.state === 'OPEN') {
      if (Date.now() - this.lastFailureTime > this.resetTimeout) {
        this.state = 'HALF-OPEN';
      } else {
        throw new Error('Circuit breaker is open');
      }
    }
    
    try {
      const result = await this.operation(...args);
      if (this.state === 'HALF-OPEN') {
        this.reset();
      }
      return result;
    } catch (error) {
      this.failureCount++;
      this.lastFailureTime = Date.now();
      
      if (this.failureCount >= this.failureThreshold) {
        this.state = 'OPEN';
      }
      throw error;
    }
  }
  
  reset() {
    this.failureCount = 0;
    this.state = 'CLOSED';
  }
}

// Usage
const apiCall = new CircuitBreaker(fetchData, { 
  failureThreshold: 3, 
  resetTimeout: 30000 
});

try {
  const data = await apiCall.exec('/api/users');
  processData(data);
} catch (error) {
  handleCircuitBreakerError(error);
}

Error Aggregation

async function executeAll(tasks) {
  const results = [];
  const errors = [];
  
  for (const task of tasks) {
    try {
      results.push(await task());
    } catch (error) {
      errors.push(error);
    }
  }
  
  if (errors.length > 0) {
    const aggregateError = new AggregateError(
      errors,
      `${errors.length} of ${tasks.length} tasks failed`
    );
    aggregateError.successfulResults = results;
    throw aggregateError;
  }
  
  return results;
}

// Usage
try {
  const results = await executeAll([
    () => fetchUsers(),
    () => fetchProducts(),
    () => fetchOrders()
  ]);
  displayResults(results);
} catch (error) {
  if (error instanceof AggregateError) {
    // Some tasks succeeded
    displayPartialResults(error.successfulResults);
    logDetailedErrors(error.errors);
  } else {
    // Something else went wrong
    handleUnexpectedError(error);
  }
}

Browser vs. Node.js Error Handling

FeatureBrowserNode.js
Global handlerswindow.onerror, window.addEventListener('error')process.on('uncaughtException')
Unhandled rejectionswindow.onunhandledrejectionprocess.on('unhandledRejection')
Error reportingconsole.error, error monitoring servicesconsole.error, logging libraries
Stack trace limitsBrowser-dependentConfigurable via Error.stackTraceLimit
Custom error captureError.captureStackTrace (Chrome)Error.captureStackTrace
Error serializationSome objects not serializableMore consistent serialization

Best Practices & Tips

General Best Practices

  • Be specific about what you catch; avoid catching too broadly
  • Always log errors with contextual information
  • Centralize error handling logic when possible
  • Use finally blocks for cleanup to avoid resource leaks
  • Don’t swallow errors without proper handling

Error Messages

  • Write clear, specific error messages
  • Include relevant context in error messages
  • Use consistent formatting for error messages
  • Consider i18n for user-facing error messages

Error Monitoring

  • Use structured logging for errors
  • Include context with every logged error
  • Consider severity levels for different errors
  • Integrate with error monitoring services (Sentry, Rollbar, etc.)

Testing Error Handling

  • Write tests that explicitly trigger errors
  • Test error recovery mechanisms
  • Use mocks to simulate various error conditions
  • Test global error handlers

Resources for Further Learning

Documentation

Books

  • “Error Handling in JavaScript” by Oliver Steele
  • “Effective JavaScript: 68 Specific Ways to Harness the Power of JavaScript” by David Herman

Online Tutorials

  • “Advanced Error Handling Techniques” on JavaScript.info
  • “JavaScript Errors and Error Handling” on W3Schools

Libraries

  • es-errors: Custom error class creation
  • verror: Error objects for Node.js
  • winston: Logging library with error handling
  • axios: HTTP client with built-in error handling
Scroll to Top