Advanced JavaScript
JavaScript Error Handling: Try, Catch, and Error Management
Master error handling in JavaScript. Learn try-catch-finally, custom errors, async error handling, and best practices for robust applications.
By JavaScriptDoc Team•
errorstry catcherror handlingdebuggingjavascript
JavaScript Error Handling: Try, Catch, and Error Management
Error handling is crucial for building robust JavaScript applications. Proper error management helps create better user experiences and makes debugging easier.
Understanding JavaScript Errors
JavaScript has several built-in error types that represent different kinds of failures.
// Common error types
console.log(new Error('Generic error'));
console.log(new SyntaxError('Syntax is incorrect'));
console.log(new ReferenceError('Variable not defined'));
console.log(new TypeError('Wrong type'));
console.log(new RangeError('Value out of range'));
console.log(new URIError('URI malformed'));
console.log(new EvalError('Eval error')); // Deprecated
// Error properties
const error = new Error('Something went wrong');
console.log(error.name); // 'Error'
console.log(error.message); // 'Something went wrong'
console.log(error.stack); // Stack trace
Try-Catch-Finally
Basic Try-Catch
// Basic try-catch
try {
// Code that may throw an error
const result = riskyOperation();
console.log('Success:', result);
} catch (error) {
// Handle the error
console.error('An error occurred:', error.message);
}
// Catching specific errors
try {
JSON.parse('invalid json');
} catch (error) {
if (error instanceof SyntaxError) {
console.error('Invalid JSON:', error.message);
} else {
console.error('Unknown error:', error);
}
}
// Multiple error scenarios
function divide(a, b) {
try {
if (typeof a !== 'number' || typeof b !== 'number') {
throw new TypeError('Arguments must be numbers');
}
if (b === 0) {
throw new Error('Division by zero');
}
return a / b;
} catch (error) {
console.error('Division error:', error.message);
return null;
}
}
Finally Block
// Finally always executes
function processFile(filename) {
let file;
try {
file = openFile(filename);
const data = file.read();
return processData(data);
} catch (error) {
console.error('Error processing file:', error);
throw error; // Re-throw if needed
} finally {
// Always cleanup
if (file) {
file.close();
console.log('File closed');
}
}
}
// Finally with return statements
function testFinally() {
try {
return 'try';
} catch (e) {
return 'catch';
} finally {
console.log('finally'); // This runs
// return 'finally'; // This would override other returns
}
}
console.log(testFinally()); // Logs: 'finally', Returns: 'try'
Throwing Errors
Basic Error Throwing
// Throwing errors
function validateAge(age) {
if (age < 0) {
throw new Error('Age cannot be negative');
}
if (age > 150) {
throw new Error('Age seems unrealistic');
}
return age;
}
// Throwing different error types
function processData(data) {
if (data === null || data === undefined) {
throw new TypeError('Data cannot be null or undefined');
}
if (!Array.isArray(data)) {
throw new TypeError('Data must be an array');
}
if (data.length === 0) {
throw new RangeError('Data array cannot be empty');
}
return data.map((item) => item * 2);
}
// Conditional error throwing
function withdraw(amount, balance) {
if (amount <= 0) {
throw new RangeError('Amount must be positive');
}
if (amount > balance) {
throw new Error('Insufficient funds');
}
return balance - amount;
}
Custom Error Classes
// Basic custom error
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = 'ValidationError';
}
}
// Custom error with additional properties
class APIError extends Error {
constructor(message, statusCode, endpoint) {
super(message);
this.name = 'APIError';
this.statusCode = statusCode;
this.endpoint = endpoint;
this.timestamp = new Date();
}
}
// Usage
try {
throw new APIError('Not Found', 404, '/api/users');
} catch (error) {
if (error instanceof APIError) {
console.log(`API Error ${error.statusCode} at ${error.endpoint}`);
}
}
// Hierarchy of custom errors
class ApplicationError extends Error {
constructor(message, statusCode) {
super(message);
this.name = 'ApplicationError';
this.statusCode = statusCode;
}
}
class UserFacingError extends ApplicationError {
constructor(message) {
super(message, 400);
this.name = 'UserFacingError';
}
}
class SystemError extends ApplicationError {
constructor(message) {
super(message, 500);
this.name = 'SystemError';
}
}
Async Error Handling
Promise Error Handling
// Promise rejection handling
fetchUserData(userId)
.then((user) => {
console.log('User:', user);
})
.catch((error) => {
console.error('Failed to fetch user:', error);
});
// Chained error handling
fetchUser(userId)
.then((user) => fetchPosts(user.id))
.then((posts) => processPosts(posts))
.catch((error) => {
// Catches errors from any step
console.error('Error in chain:', error);
});
// Specific error handling in chain
fetchData()
.then((data) => {
if (!data) {
throw new Error('No data received');
}
return processData(data);
})
.catch((error) => {
if (error.message === 'No data received') {
return getDefaultData();
}
throw error; // Re-throw other errors
})
.then((result) => {
console.log('Final result:', result);
})
.catch((error) => {
console.error('Unhandled error:', error);
});
Async/Await Error Handling
// Basic async/await error handling
async function fetchUserData(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const user = await response.json();
return user;
} catch (error) {
console.error('Error fetching user:', error);
throw error; // Re-throw if needed
}
}
// Multiple async operations
async function processUserData(userId) {
try {
const user = await fetchUser(userId);
const posts = await fetchUserPosts(user.id);
const comments = await fetchPostComments(posts);
return {
user,
posts,
comments,
};
} catch (error) {
console.error('Error processing user data:', error);
return null;
}
}
// Parallel async error handling
async function fetchMultipleUsers(userIds) {
try {
const promises = userIds.map((id) => fetchUser(id));
const users = await Promise.all(promises);
return users;
} catch (error) {
// One failure fails all
console.error('Failed to fetch users:', error);
// Alternative: handle individual failures
const results = await Promise.allSettled(
userIds.map((id) => fetchUser(id))
);
return results
.filter((result) => result.status === 'fulfilled')
.map((result) => result.value);
}
}
Error Handling Patterns
Error Boundaries Pattern
// Error boundary for function calls
function errorBoundary(fn, fallback) {
return function (...args) {
try {
return fn.apply(this, args);
} catch (error) {
console.error('Error caught by boundary:', error);
if (typeof fallback === 'function') {
return fallback(error);
}
return fallback;
}
};
}
// Usage
const safeParseJSON = errorBoundary(JSON.parse, (error) => {
console.error('JSON parse failed:', error);
return {};
});
const data = safeParseJSON('{"valid": "json"}'); // Works
const invalid = safeParseJSON('invalid json'); // Returns {}
Result Pattern
// Result wrapper pattern
class Result {
constructor(success, value, error) {
this.success = success;
this.value = value;
this.error = error;
}
static ok(value) {
return new Result(true, value, null);
}
static err(error) {
return new Result(false, null, error);
}
isOk() {
return this.success;
}
isErr() {
return !this.success;
}
unwrap() {
if (this.success) {
return this.value;
}
throw this.error;
}
unwrapOr(defaultValue) {
return this.success ? this.value : defaultValue;
}
}
// Using Result pattern
function divide(a, b) {
if (b === 0) {
return Result.err(new Error('Division by zero'));
}
return Result.ok(a / b);
}
const result = divide(10, 2);
if (result.isOk()) {
console.log('Result:', result.unwrap()); // 5
} else {
console.error('Error:', result.error.message);
}
Retry Pattern
// Retry with exponential backoff
async function retryWithBackoff(fn, maxRetries = 3, initialDelay = 1000) {
let lastError;
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
lastError = error;
console.error(`Attempt ${i + 1} failed:`, error.message);
if (i < maxRetries - 1) {
const delay = initialDelay * Math.pow(2, i);
console.log(`Retrying in ${delay}ms...`);
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
}
throw lastError;
}
// Usage
const fetchWithRetry = () =>
retryWithBackoff(
async () => {
const response = await fetch('/api/data');
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.json();
},
5, // max retries
500 // initial delay
);
Global Error Handling
Window Error Events
// Global error handler
window.addEventListener('error', (event) => {
console.error('Global error:', {
message: event.message,
filename: event.filename,
line: event.lineno,
column: event.colno,
error: event.error,
});
// Send to error tracking service
trackError({
message: event.message,
stack: event.error?.stack,
url: window.location.href,
userAgent: navigator.userAgent,
});
});
// Unhandled promise rejections
window.addEventListener('unhandledrejection', (event) => {
console.error('Unhandled promise rejection:', event.reason);
// Prevent default browser behavior
event.preventDefault();
// Handle the error
handleUnhandledRejection(event.reason);
});
// Promise rejection handling
window.addEventListener('rejectionhandled', (event) => {
console.log('Rejection handled:', event.reason);
});
Error Reporting Service
class ErrorReporter {
constructor(endpoint) {
this.endpoint = endpoint;
this.queue = [];
this.setupGlobalHandlers();
}
setupGlobalHandlers() {
// Catch all errors
window.addEventListener('error', (event) => {
this.report({
type: 'error',
message: event.message,
stack: event.error?.stack,
filename: event.filename,
line: event.lineno,
column: event.colno,
});
});
// Catch unhandled promise rejections
window.addEventListener('unhandledrejection', (event) => {
this.report({
type: 'unhandledRejection',
reason: event.reason,
promise: event.promise,
});
});
}
report(error) {
const errorReport = {
...error,
timestamp: new Date().toISOString(),
url: window.location.href,
userAgent: navigator.userAgent,
// Add more context
viewport: {
width: window.innerWidth,
height: window.innerHeight,
},
};
this.queue.push(errorReport);
this.flush();
}
async flush() {
if (this.queue.length === 0) return;
const errors = [...this.queue];
this.queue = [];
try {
await fetch(this.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ errors }),
});
} catch (error) {
// Put errors back in queue
this.queue.unshift(...errors);
console.error('Failed to report errors:', error);
}
}
}
Error Recovery Strategies
Graceful Degradation
class ResilientComponent {
constructor() {
this.errorCount = 0;
this.maxErrors = 3;
this.fallbackMode = false;
}
async loadData() {
if (this.fallbackMode) {
return this.getStaticData();
}
try {
const data = await this.fetchDynamicData();
this.errorCount = 0; // Reset on success
return data;
} catch (error) {
this.errorCount++;
console.error(
`Error loading data (${this.errorCount}/${this.maxErrors}):`,
error
);
if (this.errorCount >= this.maxErrors) {
console.warn('Switching to fallback mode');
this.fallbackMode = true;
return this.getStaticData();
}
// Try alternative source
try {
return await this.fetchFromCache();
} catch (cacheError) {
return this.getStaticData();
}
}
}
async fetchDynamicData() {
const response = await fetch('/api/data');
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.json();
}
async fetchFromCache() {
const cached = localStorage.getItem('cached_data');
if (!cached) {
throw new Error('No cached data');
}
return JSON.parse(cached);
}
getStaticData() {
return {
message: 'Using fallback data',
items: [],
};
}
}
Circuit Breaker Pattern
class CircuitBreaker {
constructor(fn, options = {}) {
this.fn = fn;
this.failureThreshold = options.failureThreshold || 5;
this.resetTimeout = options.resetTimeout || 60000;
this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
this.failures = 0;
this.nextAttempt = Date.now();
}
async call(...args) {
if (this.state === 'OPEN') {
if (Date.now() < this.nextAttempt) {
throw new Error('Circuit breaker is OPEN');
}
this.state = 'HALF_OPEN';
}
try {
const result = await this.fn(...args);
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
onSuccess() {
this.failures = 0;
this.state = 'CLOSED';
}
onFailure() {
this.failures++;
if (this.failures >= this.failureThreshold) {
this.state = 'OPEN';
this.nextAttempt = Date.now() + this.resetTimeout;
console.warn('Circuit breaker opened');
}
}
getState() {
return {
state: this.state,
failures: this.failures,
nextAttempt: this.nextAttempt,
};
}
}
// Usage
const protectedAPI = new CircuitBreaker(
async (endpoint) => {
const response = await fetch(endpoint);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.json();
},
{
failureThreshold: 3,
resetTimeout: 30000,
}
);
Debugging and Error Analysis
Enhanced Error Information
class DetailedError extends Error {
constructor(message, code, details = {}) {
super(message);
this.name = 'DetailedError';
this.code = code;
this.details = details;
this.timestamp = new Date();
// Capture additional context
this.context = {
url: window.location.href,
userAgent: navigator.userAgent,
timestamp: this.timestamp.toISOString(),
};
// Enhance stack trace
if (Error.captureStackTrace) {
Error.captureStackTrace(this, DetailedError);
}
}
toJSON() {
return {
name: this.name,
message: this.message,
code: this.code,
details: this.details,
context: this.context,
stack: this.stack,
};
}
}
// Usage
throw new DetailedError('Failed to process payment', 'PAYMENT_FAILED', {
orderId: '12345',
amount: 99.99,
currency: 'USD',
gateway: 'stripe',
attemptNumber: 3,
});
Error Logging
class ErrorLogger {
constructor() {
this.logs = [];
this.maxLogs = 1000;
}
log(error, context = {}) {
const errorLog = {
timestamp: new Date().toISOString(),
message: error.message,
stack: error.stack,
type: error.name,
context,
// Browser info
browser: {
userAgent: navigator.userAgent,
language: navigator.language,
platform: navigator.platform,
cookieEnabled: navigator.cookieEnabled,
},
// Page info
page: {
url: window.location.href,
referrer: document.referrer,
title: document.title,
},
};
this.logs.push(errorLog);
// Limit log size
if (this.logs.length > this.maxLogs) {
this.logs.shift();
}
// Also log to console in development
if (process.env.NODE_ENV === 'development') {
console.error('Error logged:', errorLog);
}
return errorLog;
}
getLogs(filter = {}) {
return this.logs.filter((log) => {
if (filter.type && log.type !== filter.type) return false;
if (filter.since && new Date(log.timestamp) < filter.since) return false;
if (filter.search && !JSON.stringify(log).includes(filter.search))
return false;
return true;
});
}
clear() {
this.logs = [];
}
export() {
return JSON.stringify(this.logs, null, 2);
}
}
Best Practices
-
Be specific with error types
// Good if (!user) { throw new Error('User not found'); } // Better if (!user) { throw new NotFoundError('User', userId); }
-
Always handle async errors
// Bad async function fetchData() { const data = await api.getData(); // Unhandled rejection } // Good async function fetchData() { try { const data = await api.getData(); return data; } catch (error) { console.error('Failed to fetch data:', error); return null; } }
-
Provide meaningful error messages
// Bad throw new Error('Invalid input'); // Good throw new Error( `Invalid email format: ${email}. Expected format: user@example.com` );
-
Clean up in finally blocks
let resource; try { resource = await acquireResource(); return await processResource(resource); } finally { if (resource) { await releaseResource(resource); } }
Conclusion
Effective error handling is essential for robust JavaScript applications:
- Try-catch-finally for synchronous error handling
- Promise catches and async/await for asynchronous errors
- Custom error classes for specific error types
- Global error handlers for uncaught errors
- Error recovery patterns for resilience
- Proper logging for debugging
Key takeaways:
- Always handle errors explicitly
- Use appropriate error types
- Provide helpful error messages
- Implement recovery strategies
- Log errors for debugging
- Test error scenarios
Master error handling to build reliable, user-friendly JavaScript applications!