Error Handling
Try/Catch/Finally
Basic Error Handling
try {
// Code that might throw
const result = riskyOperation();
console.log(result);
} catch (error) {
console.error('Error occurred:', error.message);
} finally {
console.log('Always runs');
}
Catching Specific Errors
try {
const data = JSON.parse(invalidJson);
} catch (error) {
if (error instanceof SyntaxError) {
console.error('Invalid JSON:', error.message);
} else if (error instanceof ReferenceError) {
console.error('Reference error:', error.message);
} else {
console.error('Unknown error:', error);
}
}
Nested Try/Catch
function processData(data) {
try {
const parsed = JSON.parse(data);
try {
return validateAndTransform(parsed);
} catch (validationError) {
console.warn(
'Validation failed, using defaults:',
validationError.message
);
return getDefaultData();
}
} catch (parseError) {
console.error('Failed to parse data:', parseError.message);
throw new Error('Invalid data format');
}
}
Error Types
Built-in Error Types
// Generic Error
throw new Error('Something went wrong');
// Type Error
throw new TypeError('Expected string, got number');
// Reference Error
throw new ReferenceError('Variable not defined');
// Range Error
throw new RangeError('Number out of range');
// Syntax Error (usually thrown by parser)
throw new SyntaxError('Invalid syntax');
// URI Error
throw new URIError('Invalid URI');
Custom Error Classes
// Basic custom error
class CustomError extends Error {
constructor(message) {
super(message);
this.name = 'CustomError';
}
}
// Advanced custom error
class ValidationError extends Error {
constructor(message, field, value) {
super(message);
this.name = 'ValidationError';
this.field = field;
this.value = value;
}
}
// HTTP Error
class HTTPError extends Error {
constructor(message, status, response) {
super(message);
this.name = 'HTTPError';
this.status = status;
this.response = response;
}
}
// Usage
try {
throw new ValidationError('Invalid email', 'email', 'invalid-email');
} catch (error) {
if (error instanceof ValidationError) {
console.log(`Validation failed for ${error.field}: ${error.value}`);
}
}
Error with Stack Trace
class AppError extends Error {
constructor(message, code, statusCode = 500) {
super(message);
this.name = 'AppError';
this.code = code;
this.statusCode = statusCode;
// Maintain proper stack trace
if (Error.captureStackTrace) {
Error.captureStackTrace(this, AppError);
}
}
}
// Usage
function criticalOperation() {
throw new AppError('Database connection failed', 'DB_ERROR', 503);
}
try {
criticalOperation();
} catch (error) {
console.log('Error code:', error.code);
console.log('Status code:', error.statusCode);
console.log('Stack trace:', error.stack);
}
Promise Error Handling
Promise Chain Error Handling
// Basic promise error handling
fetchData()
.then(data => processData(data))
.then(result => displayResult(result))
.catch(error => {
console.error('Promise rejected:', error);
displayError(error.message);
});
// Error recovery in promise chain
fetchData()
.then(data => processData(data))
.catch(error => {
console.warn('Primary data source failed, trying backup');
return fetchBackupData(); // Return recovery data
})
.then(data => displayResult(data))
.catch(error => {
console.error('All data sources failed:', error);
displayFallbackContent();
});
Multiple Catch Handlers
fetchData()
.then(data => {
if (!data) {
throw new Error('No data received');
}
return processData(data);
})
.catch(error => {
if (error.message === 'No data received') {
console.log('Handling no data scenario');
return getDefaultData();
}
throw error; // Re-throw if not handled
})
.then(data => displayResult(data))
.catch(error => {
console.error('Unhandled error:', error);
});
Async/Await Error Handling
// Basic async/await error handling
async function fetchAndProcess() {
try {
const data = await fetchData();
const processed = await processData(data);
return processed;
} catch (error) {
console.error('Error in fetchAndProcess:', error);
throw error; // Re-throw or handle
}
}
// Multiple operations with specific error handling
async function complexOperation() {
let userData, settings, preferences;
try {
userData = await fetchUserData();
} catch (error) {
console.error('Failed to fetch user data:', error);
userData = getDefaultUserData();
}
try {
settings = await fetchUserSettings(userData.id);
} catch (error) {
console.error('Failed to fetch settings:', error);
settings = getDefaultSettings();
}
try {
preferences = await fetchUserPreferences(userData.id);
} catch (error) {
console.error('Failed to fetch preferences:', error);
preferences = getDefaultPreferences();
}
return { userData, settings, preferences };
}
Promise.allSettled for Error Tolerance
async function fetchAllDataWithErrorHandling() {
const results = await Promise.allSettled([
fetchUserData(),
fetchUserPosts(),
fetchUserFriends(),
]);
const userData =
results[0].status === 'fulfilled' ? results[0].value : getDefaultUserData();
const posts = results[1].status === 'fulfilled' ? results[1].value : [];
const friends = results[2].status === 'fulfilled' ? results[2].value : [];
// Log any failures
results.forEach((result, index) => {
if (result.status === 'rejected') {
console.error(`Operation ${index} failed:`, result.reason);
}
});
return { userData, posts, friends };
}
Error Handling Patterns
Error Boundary Pattern
class ErrorHandler {
static handle(error, context = 'Unknown') {
console.error(`Error in ${context}:`, error);
// Log to external service
this.logError(error, context);
// Show user-friendly message
this.showUserMessage(error);
}
static logError(error, context) {
// Send to logging service
const errorData = {
message: error.message,
stack: error.stack,
context,
timestamp: new Date().toISOString(),
userAgent: navigator.userAgent,
url: window.location.href,
};
// Example: send to logging service
// logService.log(errorData);
}
static showUserMessage(error) {
let message = 'Something went wrong. Please try again.';
if (error instanceof ValidationError) {
message = `Please check your ${error.field}.`;
} else if (error instanceof HTTPError) {
if (error.status === 404) {
message = 'The requested resource was not found.';
} else if (error.status >= 500) {
message = 'Server error. Please try again later.';
}
}
// Show message to user
showToast(message, 'error');
}
}
// Usage
try {
await performCriticalOperation();
} catch (error) {
ErrorHandler.handle(error, 'Critical Operation');
}
Retry Pattern with Exponential Backoff
async function retryWithBackoff(operation, maxRetries = 3, baseDelay = 1000) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
if (attempt === maxRetries) {
throw new Error(
`Operation failed after ${maxRetries} attempts: ${error.message}`
);
}
const delay = baseDelay * Math.pow(2, attempt - 1);
console.log(`Attempt ${attempt} failed, retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
// Usage
try {
const data = await retryWithBackoff(
() => fetch('/api/unreliable').then(r => r.json()),
3,
1000
);
} catch (error) {
console.error('All retry attempts failed:', error);
}
Circuit Breaker Pattern
class CircuitBreaker {
constructor(threshold = 5, timeout = 60000) {
this.threshold = threshold;
this.timeout = timeout;
this.failureCount = 0;
this.lastFailureTime = null;
this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
}
async execute(operation) {
if (this.state === 'OPEN') {
if (Date.now() - this.lastFailureTime < this.timeout) {
throw new Error('Circuit breaker is OPEN');
}
this.state = 'HALF_OPEN';
}
try {
const result = await operation();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
onSuccess() {
this.failureCount = 0;
this.state = 'CLOSED';
}
onFailure() {
this.failureCount++;
this.lastFailureTime = Date.now();
if (this.failureCount >= this.threshold) {
this.state = 'OPEN';
}
}
}
// Usage
const circuitBreaker = new CircuitBreaker(3, 30000);
async function makeAPICall() {
try {
return await circuitBreaker.execute(() =>
fetch('/api/service').then(r => r.json())
);
} catch (error) {
console.error('API call failed:', error.message);
return getDefaultData();
}
}
Graceful Degradation
class ServiceWithFallbacks {
async getData(id) {
// Try primary service
try {
return await this.primaryService.getData(id);
} catch (error) {
console.warn('Primary service failed:', error.message);
}
// Try secondary service
try {
return await this.secondaryService.getData(id);
} catch (error) {
console.warn('Secondary service failed:', error.message);
}
// Try cache
try {
const cached = await this.cache.get(id);
if (cached) {
console.log('Serving from cache');
return cached;
}
} catch (error) {
console.warn('Cache failed:', error.message);
}
// Final fallback
console.log('All services failed, using default data');
return this.getDefaultData(id);
}
getDefaultData(id) {
return {
id,
data: 'Default content',
source: 'fallback',
};
}
}
Error Logging and Monitoring
class ErrorLogger {
static logError(error, context = {}) {
const errorInfo = {
message: error.message,
stack: error.stack,
name: error.name,
timestamp: new Date().toISOString(),
url: window.location.href,
userAgent: navigator.userAgent,
userId: context.userId,
action: context.action,
additional: context.additional,
};
// Log to console in development
if (process.env.NODE_ENV === 'development') {
console.error('Error logged:', errorInfo);
}
// Send to external logging service
this.sendToLoggingService(errorInfo);
}
static async sendToLoggingService(errorInfo) {
try {
await fetch('/api/logs', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(errorInfo),
});
} catch (loggingError) {
console.error('Failed to log error:', loggingError);
}
}
}
// Global error handler
window.addEventListener('error', event => {
ErrorLogger.logError(event.error, {
action: 'global_error',
additional: {
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
},
});
});
// Unhandled promise rejection handler
window.addEventListener('unhandledrejection', event => {
ErrorLogger.logError(event.reason, {
action: 'unhandled_promise_rejection',
});
});