Error Handling (try...catch)
Proper error handling is crucial for building robust JavaScript applications. The try...catch statement provides a way to handle errors gracefully.
Basic Try...Catch Syntax
Simple Error Handling
javascript// Basic try...catch structure try { // Code that might throw an error let result = riskyOperation(); console.log('Success:', result); } catch (error) { // Handle the error console.error('Error occurred:', error.message); } // Example with actual error try { let data = JSON.parse('invalid json'); console.log(data); } catch (error) { console.error('JSON parsing failed:', error.message); // Output: JSON parsing failed: Unexpected token i in JSON at position 0 } // Code continues after try...catch console.log('Program continues...');
Finally Block
javascript// try...catch...finally function processData(data) { let file = null; try { file = openFile('data.txt'); let parsed = JSON.parse(data); return processFile(file, parsed); } catch (error) { console.error('Processing failed:', error.message); return null; } finally { // Always executes, regardless of success or failure if (file) { closeFile(file); console.log('File closed'); } console.log('Cleanup completed'); } } // Helper functions for example function openFile(filename) { console.log(`Opening ${filename}`); return { name: filename, handle: 'file-handle' }; } function closeFile(file) { console.log(`Closing ${file.name}`); } function processFile(file, data) { return `Processed ${Object.keys(data).length} properties`; } // Test with valid and invalid data console.log(processData('{"name": "John", "age": 30}')); console.log(processData('invalid json'));
Types of Errors
Built-in Error Types
javascript// Different types of errors try { // ReferenceError - variable doesn't exist console.log(undefinedVariable); } catch (error) { console.log('Error type:', error.constructor.name); // ReferenceError console.log('Message:', error.message); } try { // TypeError - wrong type operation null.someMethod(); } catch (error) { console.log('Error type:', error.constructor.name); // TypeError console.log('Message:', error.message); } try { // SyntaxError - invalid syntax (usually caught at parse time) eval('let x = ;'); } catch (error) { console.log('Error type:', error.constructor.name); // SyntaxError console.log('Message:', error.message); } try { // RangeError - number out of range let arr = new Array(-1); } catch (error) { console.log('Error type:', error.constructor.name); // RangeError console.log('Message:', error.message); } try { // URIError - invalid URI decodeURIComponent('%'); } catch (error) { console.log('Error type:', error.constructor.name); // URIError console.log('Message:', error.message); }
Custom Errors
javascript// Creating custom error classes class ValidationError extends Error { constructor(message, field) { super(message); this.name = 'ValidationError'; this.field = field; } } class NetworkError extends Error { constructor(message, statusCode) { super(message); this.name = 'NetworkError'; this.statusCode = statusCode; } } class AuthenticationError extends Error { constructor(message) { super(message); this.name = 'AuthenticationError'; } } // Using custom errors function validateUser(user) { if (!user.name) { throw new ValidationError('Name is required', 'name'); } if (!user.email || !user.email.includes('@')) { throw new ValidationError('Valid email is required', 'email'); } if (user.age < 0 || user.age > 150) { throw new ValidationError('Age must be between 0 and 150', 'age'); } return true; } // Handling custom errors function processUser(userData) { try { validateUser(userData); console.log('User is valid:', userData.name); return userData; } catch (error) { if (error instanceof ValidationError) { console.error(`Validation failed for ${error.field}: ${error.message}`); } else { console.error('Unexpected error:', error.message); } return null; } } // Test custom errors processUser({ name: 'John', email: 'john@example.com', age: 30 }); // Valid processUser({ name: '', email: 'invalid-email', age: -5 }); // Invalid
Error Handling Patterns
Multiple Catch Blocks (Simulation)
javascript// JavaScript doesn't have multiple catch blocks, but we can simulate them function handleMultipleErrors(operation) { try { return operation(); } catch (error) { // Handle different error types if (error instanceof ValidationError) { console.error('Validation Error:', error.message); return { success: false, type: 'validation', field: error.field }; } else if (error instanceof NetworkError) { console.error('Network Error:', error.message, 'Status:', error.statusCode); return { success: false, type: 'network', status: error.statusCode }; } else if (error instanceof AuthenticationError) { console.error('Authentication Error:', error.message); return { success: false, type: 'auth' }; } else { console.error('Unknown Error:', error.message); return { success: false, type: 'unknown', error: error.message }; } } } // Test different error types function testOperation1() { throw new ValidationError('Invalid input', 'username'); } function testOperation2() { throw new NetworkError('Connection failed', 500); } function testOperation3() { throw new AuthenticationError('Invalid credentials'); } function testOperation4() { throw new Error('Something went wrong'); } console.log(handleMultipleErrors(testOperation1)); console.log(handleMultipleErrors(testOperation2)); console.log(handleMultipleErrors(testOperation3)); console.log(handleMultipleErrors(testOperation4));
Nested Try...Catch
javascriptfunction complexOperation(data) { try { console.log('Starting complex operation...'); // First level operation let parsed; try { parsed = JSON.parse(data); console.log('Data parsed successfully'); } catch (parseError) { console.error('JSON parsing failed:', parseError.message); throw new ValidationError('Invalid JSON format', 'data'); } // Second level operation let processed; try { processed = processComplexData(parsed); console.log('Data processed successfully'); } catch (processError) { console.error('Processing failed:', processError.message); throw new Error('Data processing failed'); } // Third level operation try { let result = finalizeData(processed); console.log('Operation completed successfully'); return result; } catch (finalizeError) { console.error('Finalization failed:', finalizeError.message); throw new Error('Could not finalize data'); } } catch (error) { console.error('Complex operation failed:', error.message); // Log additional context console.error('Stack trace:', error.stack); // Return safe fallback return { success: false, error: error.message }; } } function processComplexData(data) { if (!data.id) { throw new Error('Missing required ID field'); } return { ...data, processed: true, timestamp: Date.now() }; } function finalizeData(data) { if (!data.processed) { throw new Error('Data not properly processed'); } return { ...data, finalized: true }; } // Test nested error handling console.log(complexOperation('{"id": 1, "name": "Test"}')); console.log(complexOperation('invalid json')); console.log(complexOperation('{"name": "Test"}'));
Async Error Handling
Try...Catch with Async/Await
javascript// Async function that might fail async function fetchUserData(userId) { // Simulate network delay await new Promise(resolve => setTimeout(resolve, 500)); if (userId <= 0) { throw new ValidationError('Invalid user ID', 'userId'); } if (userId === 999) { throw new NetworkError('User not found', 404); } return { id: userId, name: `User ${userId}`, email: `user${userId}@example.com` }; } // Handling async errors async function getUserSafely(userId) { try { console.log(`Fetching user ${userId}...`); const user = await fetchUserData(userId); console.log('User fetched:', user.name); return user; } catch (error) { if (error instanceof ValidationError) { console.error('Invalid input:', error.message); } else if (error instanceof NetworkError) { console.error(`Network error (${error.statusCode}): ${error.message}`); } else { console.error('Unexpected error:', error.message); } return null; } } // Multiple async operations async function fetchMultipleUsers(userIds) { const results = []; for (const userId of userIds) { try { const user = await fetchUserData(userId); results.push({ success: true, user }); } catch (error) { results.push({ success: false, userId, error: error.message, type: error.constructor.name }); } } return results; } // Test async error handling async function testAsyncErrors() { await getUserSafely(1); // Success await getUserSafely(-1); // Validation error await getUserSafely(999); // Network error const results = await fetchMultipleUsers([1, -1, 2, 999, 3]); console.log('Batch results:', results); } // testAsyncErrors();
Promise Error Handling
javascript// Promise-based error handling function fetchDataPromise(id) { return new Promise((resolve, reject) => { setTimeout(() => { if (id > 0) { resolve({ id, data: `Data for ${id}` }); } else { reject(new ValidationError('Invalid ID', 'id')); } }, 300); }); } // Using .catch() with promises fetchDataPromise(1) .then(data => { console.log('Promise success:', data); return data; }) .catch(error => { console.error('Promise error:', error.message); return null; // Fallback value }) .then(result => { console.log('Final result:', result); }); // Converting promise errors to try...catch async function handlePromiseWithTryCatch(id) { try { const data = await fetchDataPromise(id); console.log('Async success:', data); return data; } catch (error) { console.error('Async error:', error.message); return null; } } // Promise.all error handling async function fetchMultiplePromises(ids) { try { const promises = ids.map(id => fetchDataPromise(id)); const results = await Promise.all(promises); console.log('All promises resolved:', results); return results; } catch (error) { console.error('One or more promises failed:', error.message); return []; } } // Promise.allSettled for handling mixed results async function fetchWithAllSettled(ids) { const promises = ids.map(id => fetchDataPromise(id)); const results = await Promise.allSettled(promises); const successful = results .filter(result => result.status === 'fulfilled') .map(result => result.value); const failed = results .filter(result => result.status === 'rejected') .map(result => ({ error: result.reason.message, type: result.reason.constructor.name })); return { successful, failed }; } // Test promise error handling async function testPromiseErrors() { await handlePromiseWithTryCatch(1); await handlePromiseWithTryCatch(-1); await fetchMultiplePromises([1, 2, 3]); await fetchMultiplePromises([1, -1, 3]); // Will fail fast const mixedResults = await fetchWithAllSettled([1, -1, 2, -2, 3]); console.log('Mixed results:', mixedResults); } // testPromiseErrors();
Practical Error Handling Examples
Form Validation with Error Handling
javascriptclass FormValidator { constructor() { this.errors = []; } validateField(value, rules, fieldName) { const fieldErrors = []; try { // Required validation if (rules.required && (!value || value.trim() === '')) { throw new ValidationError(`${fieldName} is required`, fieldName); } // Length validation if (rules.minLength && value.length < rules.minLength) { throw new ValidationError( `${fieldName} must be at least ${rules.minLength} characters`, fieldName ); } if (rules.maxLength && value.length > rules.maxLength) { throw new ValidationError( `${fieldName} must not exceed ${rules.maxLength} characters`, fieldName ); } // Email validation if (rules.email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) { throw new ValidationError(`${fieldName} must be a valid email`, fieldName); } // Custom validation if (rules.custom && typeof rules.custom === 'function') { const customResult = rules.custom(value); if (customResult !== true) { throw new ValidationError(customResult || `${fieldName} is invalid`, fieldName); } } } catch (error) { if (error instanceof ValidationError) { fieldErrors.push(error); } else { fieldErrors.push(new ValidationError(`Validation error in ${fieldName}`, fieldName)); } } return fieldErrors; } validateForm(formData, rules) { this.errors = []; for (const [fieldName, fieldRules] of Object.entries(rules)) { const value = formData[fieldName]; const fieldErrors = this.validateField(value, fieldRules, fieldName); this.errors.push(...fieldErrors); } return { isValid: this.errors.length === 0, errors: this.errors, errorsByField: this.groupErrorsByField() }; } groupErrorsByField() { const grouped = {}; this.errors.forEach(error => { if (!grouped[error.field]) { grouped[error.field] = []; } grouped[error.field].push(error.message); }); return grouped; } } // Usage example const validator = new FormValidator(); const formData = { name: '', email: 'invalid-email', password: '123', confirmPassword: '456' }; const rules = { name: { required: true, minLength: 2, maxLength: 50 }, email: { required: true, email: true }, password: { required: true, minLength: 8, custom: (value) => { if (!/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/.test(value)) { return 'Password must contain uppercase, lowercase, and number'; } return true; } }, confirmPassword: { required: true, custom: (value) => { if (value !== formData.password) { return 'Passwords do not match'; } return true; } } }; const result = validator.validateForm(formData, rules); console.log('Validation result:', result);
API Error Handler
javascriptclass ApiErrorHandler { static handle(error, context = '') { const errorInfo = { timestamp: new Date().toISOString(), context, type: error.constructor.name, message: error.message }; try { if (error instanceof NetworkError) { return this.handleNetworkError(error, errorInfo); } else if (error instanceof ValidationError) { return this.handleValidationError(error, errorInfo); } else if (error instanceof AuthenticationError) { return this.handleAuthError(error, errorInfo); } else if (error.name === 'TypeError') { return this.handleTypeError(error, errorInfo); } else { return this.handleGenericError(error, errorInfo); } } catch (handlingError) { console.error('Error in error handler:', handlingError); return { success: false, error: 'Error handling failed', originalError: error.message }; } } static handleNetworkError(error, errorInfo) { console.error(`Network Error [${errorInfo.context}]:, error.message`); const response = { success: false, error: 'Network error occurred', canRetry: true, ...errorInfo }; // Specific handling based on status code if (error.statusCode >= 500) { response.error = 'Server error - please try again later'; response.severity = 'high'; } else if (error.statusCode === 404) { response.error = 'Resource not found'; response.canRetry = false; response.severity = 'medium'; } else if (error.statusCode === 401) { response.error = 'Authentication required'; response.canRetry = false; response.severity = 'high'; response.requiresAuth = true; } return response; } static handleValidationError(error, errorInfo) { console.warn(`Validation Error [${errorInfo.context}]:, error.message`); return { success: false, error: 'Validation failed', field: error.field, canRetry: false, severity: 'low', ...errorInfo }; } static handleAuthError(error, errorInfo) { console.error(`Auth Error [${errorInfo.context}]:, error.message`); return { success: false, error: 'Authentication failed', canRetry: false, severity: 'high', requiresAuth: true, ...errorInfo }; } static handleTypeError(error, errorInfo) { console.error(`Type Error [${errorInfo.context}]:, error.message`); return { success: false, error: 'Invalid data type', canRetry: false, severity: 'medium', ...errorInfo }; } static handleGenericError(error, errorInfo) { console.error(`Generic Error [${errorInfo.context}]:, error.message`); console.error('Stack trace:', error.stack); return { success: false, error: 'An unexpected error occurred', canRetry: true, severity: 'medium', ...errorInfo }; } } // Usage in API calls async function apiCall(endpoint, options = {}) { try { const response = await fetch(endpoint, options); if (!response.ok) { throw new NetworkError( `Request failed: ${response.statusText}`, response.status ); } const data = await response.json(); return { success: true, data }; } catch (error) { return ApiErrorHandler.handle(error, `API call to ${endpoint}`); } } // Test API error handling async function testApiErrorHandling() { // Simulate different types of errors const results = await Promise.allSettled([ apiCall('https://jsonplaceholder.typicode.com/posts/1'), apiCall('https://jsonplaceholder.typicode.com/posts/999'), apiCall('https://invalid-url-that-will-fail.com/api') ]); results.forEach((result, index) => { if (result.status === 'fulfilled') { console.log(`API call ${index + 1}:, result.value`); } else { console.log(`API call ${index + 1} failed:, result.reason`); } }); } // testApiErrorHandling();
Error Logging and Monitoring
javascriptclass ErrorLogger { constructor() { this.logs = []; this.maxLogs = 1000; } log(error, context = '', severity = 'medium') { const logEntry = { id: this.generateId(), timestamp: new Date().toISOString(), error: { name: error.name, message: error.message, stack: error.stack }, context, severity, userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : 'Node.js', url: typeof window !== 'undefined' ? window.location.href : 'N/A' }; this.logs.push(logEntry); // Keep only recent logs if (this.logs.length > this.maxLogs) { this.logs = this.logs.slice(-this.maxLogs); } // Console output based on severity switch (severity) { case 'low': console.info('Error logged:', logEntry); break; case 'medium': console.warn('Error logged:', logEntry); break; case 'high': console.error('Critical error logged:', logEntry); break; } // Send to monitoring service (simulated) this.sendToMonitoring(logEntry); return logEntry.id; } generateId() { return Date.now().toString(36) + Math.random().toString(36).substr(2); } async sendToMonitoring(logEntry) { try { // Simulate sending to monitoring service console.log('Sending to monitoring service:', logEntry.id); // In real implementation, you might use: // await fetch('/api/errors', { // method: 'POST', // headers: { 'Content-Type': 'application/json' }, // body: JSON.stringify(logEntry) // }); } catch (monitoringError) { console.error('Failed to send error to monitoring:', monitoringError); } } getRecentErrors(count = 10) { return this.logs.slice(-count); } getErrorsByContext(context) { return this.logs.filter(log => log.context.includes(context)); } getErrorsBySeverity(severity) { return this.logs.filter(log => log.severity === severity); } clearLogs() { this.logs = []; } } // Global error logger instance const errorLogger = new ErrorLogger(); // Global error handlers if (typeof window !== 'undefined') { // Browser environment window.addEventListener('error', (event) => { errorLogger.log( new Error(event.message), `Global error at ${event.filename}:${event.lineno}`, 'high' ); }); window.addEventListener('unhandledrejection', (event) => { errorLogger.log( event.reason, 'Unhandled promise rejection', 'high' ); }); } else { // Node.js environment process.on('uncaughtException', (error) => { errorLogger.log(error, 'Uncaught exception', 'high'); process.exit(1); }); process.on('unhandledRejection', (reason, promise) => { errorLogger.log( reason instanceof Error ? reason : new Error(String(reason)), 'Unhandled promise rejection', 'high' ); }); } // Usage example function demonstrateErrorLogging() { try { throw new ValidationError('Test validation error', 'testField'); } catch (error) { const logId = errorLogger.log(error, 'Error logging demonstration', 'low'); console.log('Error logged with ID:', logId); } // View recent errors console.log('Recent errors:', errorLogger.getRecentErrors(5)); } // demonstrateErrorLogging();
Best Practices
Error Handling Guidelines
javascript// 1. Always handle errors appropriately function goodErrorHandling() { try { // Risky operation let result = JSON.parse(getData()); return processResult(result); } catch (error) { // Log the error console.error('Error in goodErrorHandling:', error); // Return safe fallback return getDefaultResult(); } } // 2. Don't catch and ignore errors function badErrorHandling() { try { riskyOperation(); } catch (error) { // BAD: Silently ignoring errors // This makes debugging very difficult } } // 3. Provide meaningful error messages function createMeaningfulErrors() { if (!user.id) { throw new ValidationError( 'User ID is required for profile update', 'userId' ); } if (user.age < 0) { throw new ValidationError( `Invalid age value: ${user.age}. Age must be a positive number.`, 'age' ); } } // 4. Use specific error types function useSpecificErrors(operation) { try { return operation(); } catch (error) { if (error instanceof ValidationError) { // Handle validation errors return handleValidationError(error); } else if (error instanceof NetworkError) { // Handle network errors return handleNetworkError(error); } else { // Handle unexpected errors return handleUnexpectedError(error); } } } // Helper functions for examples function getData() { return '{"valid": "json"}'; } function processResult(result) { return `Processed: ${result.valid}`; } function getDefaultResult() { return 'Default result'; } function riskyOperation() { throw new Error('Something went wrong'); } function handleValidationError(error) { return { success: false, type: 'validation', message: error.message }; } function handleNetworkError(error) { return { success: false, type: 'network', message: error.message }; } function handleUnexpectedError(error) { return { success: false, type: 'unexpected', message: error.message }; } const user = { id: null, age: -5 }; // Test the examples console.log(goodErrorHandling()); // createMeaningfulErrors(); // Would throw errors