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
- Try Block: Contains code that might throw an error
- Catch Block: Executes if an error occurs in the try block
- 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 Type | Description | Common Trigger |
|---|
Error | Base error type | Manual throws |
SyntaxError | Invalid syntax | Parsing invalid code |
ReferenceError | Reference to undefined variable | Using undeclared variables |
TypeError | Value of wrong type | Calling non-functions |
RangeError | Value outside allowed range | Infinite recursion |
URIError | Invalid URI | Malformed decodeURI() |
EvalError | Error with eval() function | Rare in modern JS |
AggregateError | Multiple errors wrapped | Promise.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
| Challenge | Solution |
|---|
| Too many try/catch blocks | Group related operations; use higher-order functions |
| Losing the original error | Attach original error as a property of new error |
| Inconsistent error formats | Standardize errors through a central error factory |
| Missing stack traces | Ensure Error.captureStackTrace() is used in custom errors |
| Asynchronous errors being missed | Use async/await with try/catch or ensure Promise chains have .catch() |
| Errors in event handlers | Wrap handlers with try/catch or use global error listener |
| Non-descriptive error messages | Create 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
| Feature | Browser | Node.js |
|---|
| Global handlers | window.onerror, window.addEventListener('error') | process.on('uncaughtException') |
| Unhandled rejections | window.onunhandledrejection | process.on('unhandledRejection') |
| Error reporting | console.error, error monitoring services | console.error, logging libraries |
| Stack trace limits | Browser-dependent | Configurable via Error.stackTraceLimit |
| Custom error capture | Error.captureStackTrace (Chrome) | Error.captureStackTrace |
| Error serialization | Some objects not serializable | More 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