Advanced Error Handling
Learning Objectives
- By the end of this lesson, you will be able to:
- - Understand different error types
- - Create custom error classes
- - Implement error handling strategies
- - Set up global error handling
- - Handle errors in async code
- - Build robust error handling systems
- - Write more reliable code
Lesson 20.1: Advanced Error Handling
Learning Objectives
By the end of this lesson, you will be able to:
- Understand different error types
- Create custom error classes
- Implement error handling strategies
- Set up global error handling
- Handle errors in async code
- Build robust error handling systems
- Write more reliable code
Introduction to Error Handling
Error handling is crucial for building robust applications. Proper error handling improves user experience and makes debugging easier.
Why Error Handling?
- User Experience: Graceful error messages
- Debugging: Easier to find and fix issues
- Reliability: Applications don't crash unexpectedly
- Maintainability: Clear error information
- Best Practices: Professional code quality
- Production Ready: Handle edge cases
Error Types
Built-in Error Types
// Error (base class)
try {
throw new Error('Something went wrong');
} catch (error) {
console.log(error.name); // 'Error'
console.log(error.message); // 'Something went wrong'
console.log(error.stack); // Stack trace
}
// TypeError
try {
null.someProperty;
} catch (error) {
console.log(error.name); // 'TypeError'
}
// ReferenceError
try {
console.log(undefinedVariable);
} catch (error) {
console.log(error.name); // 'ReferenceError'
}
// SyntaxError
try {
eval('invalid syntax {');
} catch (error) {
console.log(error.name); // 'SyntaxError'
}
// RangeError
try {
let arr = new Array(-1);
} catch (error) {
console.log(error.name); // 'RangeError'
}
Common Error Types
// TypeError: Wrong type
let num = 'hello';
num.toUpperCase(); // Works
num.toFixed(); // TypeError: num.toFixed is not a function
// ReferenceError: Variable doesn't exist
console.log(undefinedVar); // ReferenceError
// SyntaxError: Invalid syntax
// let x = {; // SyntaxError
// RangeError: Out of range
let arr = new Array(-1); // RangeError
// URIError: Invalid URI
decodeURIComponent('%'); // URIError
Custom Errors
Basic Custom Error
class CustomError extends Error {
constructor(message) {
super(message);
this.name = 'CustomError';
Error.captureStackTrace(this, this.constructor);
}
}
try {
throw new CustomError('Custom error message');
} catch (error) {
console.log(error.name); // 'CustomError'
console.log(error.message); // 'Custom error message'
}
Custom Error with Additional Properties
class ValidationError extends Error {
constructor(message, field, value) {
super(message);
this.name = 'ValidationError';
this.field = field;
this.value = value;
Error.captureStackTrace(this, this.constructor);
}
}
try {
throw new ValidationError('Invalid email', 'email', 'invalid');
} catch (error) {
if (error instanceof ValidationError) {
console.log(`Field: ${error.field}`);
console.log(`Value: ${error.value}`);
console.log(`Message: ${error.message}`);
}
}
Multiple Custom Error Types
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';
}
}
class NotFoundError extends Error {
constructor(resource) {
super(`${resource} not found`);
this.name = 'NotFoundError';
this.resource = resource;
}
}
// Usage
try {
throw new NetworkError('Connection failed', 500);
} catch (error) {
if (error instanceof NetworkError) {
console.log(`Network error: ${error.message} (${error.statusCode})`);
} else if (error instanceof AuthenticationError) {
console.log('Authentication failed');
} else if (error instanceof NotFoundError) {
console.log(`${error.resource} not found`);
}
}
Error Handling Strategies
Try-Catch-Finally
function riskyOperation() {
try {
// Code that might throw
let result = someFunction();
return result;
} catch (error) {
// Handle error
console.error('Error occurred:', error.message);
throw error; // Re-throw if needed
} finally {
// Always executes
console.log('Cleanup code');
}
}
Error Handling in Functions
function divide(a, b) {
if (b === 0) {
throw new Error('Division by zero');
}
return a / b;
}
function safeDivide(a, b) {
try {
return divide(a, b);
} catch (error) {
console.error('Division error:', error.message);
return null; // Or return default value
}
}
console.log(safeDivide(10, 2)); // 5
console.log(safeDivide(10, 0)); // null (error handled)
Error Handling in Async Code
// Async function with try-catch
async function fetchData(url) {
try {
let response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Fetch error:', error.message);
throw error;
}
}
// Promise with catch
fetchData('/api/data')
.then(data => {
console.log('Data:', data);
})
.catch(error => {
console.error('Error:', error.message);
});
Error Handling Patterns
// Pattern 1: Return error object
function safeOperation() {
try {
let result = riskyOperation();
return { success: true, data: result };
} catch (error) {
return { success: false, error: error.message };
}
}
// Pattern 2: Throw and handle
function operation() {
try {
return riskyOperation();
} catch (error) {
// Log error
console.error(error);
// Re-throw or handle
throw error;
}
}
// Pattern 3: Default values
function getValue(key) {
try {
return getValueFromSource(key);
} catch (error) {
return defaultValue; // Return default
}
}
Global Error Handling
Window Error Handler
// Global error handler
window.addEventListener('error', function(event) {
console.error('Global error:', event.error);
console.error('Message:', event.message);
console.error('Source:', event.filename);
console.error('Line:', event.lineno);
console.error('Column:', event.colno);
// Send to error tracking service
// sendToErrorTracking(event.error);
// Prevent default error handling
// event.preventDefault();
});
// Unhandled promise rejection
window.addEventListener('unhandledrejection', function(event) {
console.error('Unhandled promise rejection:', event.reason);
// Send to error tracking
// sendToErrorTracking(event.reason);
// Prevent default handling
// event.preventDefault();
});
Node.js Error Handling
// Node.js global handlers
process.on('uncaughtException', (error) => {
console.error('Uncaught exception:', error);
// Cleanup and exit
process.exit(1);
});
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled rejection:', reason);
// Handle or exit
});
Error Boundary (React)
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
console.error('Error caught:', error, errorInfo);
// Log to error tracking service
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
Error Handling Best Practices
1. Be Specific
// ❌ Bad: Generic error
throw new Error('Error');
// ✅ Good: Specific error
throw new ValidationError('Email is required', 'email');
2. Don't Swallow Errors
// ❌ Bad: Swallowing error
try {
riskyOperation();
} catch (error) {
// Silent failure
}
// ✅ Good: Handle or log
try {
riskyOperation();
} catch (error) {
console.error('Error:', error);
// Handle appropriately
}
3. Use Appropriate Error Types
// ❌ Bad: Wrong error type
throw new Error('Network error');
// ✅ Good: Specific error type
throw new NetworkError('Connection failed', 500);
4. Provide Context
// ❌ Bad: No context
throw new Error('Failed');
// ✅ Good: With context
throw new Error(`Failed to fetch user ${userId}: ${error.message}`);
5. Handle Errors at Appropriate Level
// Handle at the right level
async function processUser(userId) {
try {
let user = await fetchUser(userId);
return processUserData(user);
} catch (error) {
// Handle at this level
if (error instanceof NotFoundError) {
return null; // User not found
}
throw error; // Re-throw other errors
}
}
Practice Exercise
Exercise: Error Handling
Objective: Practice creating custom errors, implementing error handling strategies, and setting up global error handling.
Instructions:
- Create a JavaScript file
- Practice:
- Creating custom error classes
- Implementing error handling
- Setting up global handlers
- Handling async errors
Example Solution:
// error-handling-practice.js
console.log("=== Error Handling Practice ===");
console.log("\n=== Built-in Error Types ===");
// Error
try {
throw new Error('Something went wrong');
} catch (error) {
console.log('Error:', error.name, error.message);
}
// TypeError
try {
let num = 'hello';
num.toFixed();
} catch (error) {
console.log('TypeError:', error.name);
}
// ReferenceError
try {
console.log(undefinedVar);
} catch (error) {
console.log('ReferenceError:', error.name);
}
console.log();
console.log("=== Custom Errors ===");
// Basic custom error
class CustomError extends Error {
constructor(message) {
super(message);
this.name = 'CustomError';
Error.captureStackTrace(this, this.constructor);
}
}
try {
throw new CustomError('Custom error message');
} catch (error) {
console.log('Custom error:', error.name, error.message);
}
// Validation error
class ValidationError extends Error {
constructor(message, field, value) {
super(message);
this.name = 'ValidationError';
this.field = field;
this.value = value;
}
}
try {
throw new ValidationError('Invalid email', 'email', 'invalid');
} catch (error) {
if (error instanceof ValidationError) {
console.log(`Validation error: ${error.field} = ${error.value}`);
}
}
// Network error
class NetworkError extends Error {
constructor(message, statusCode) {
super(message);
this.name = 'NetworkError';
this.statusCode = statusCode;
}
}
// Authentication error
class AuthenticationError extends Error {
constructor(message) {
super(message);
this.name = 'AuthenticationError';
}
}
// Not found error
class NotFoundError extends Error {
constructor(resource) {
super(`${resource} not found`);
this.name = 'NotFoundError';
this.resource = resource;
}
}
// Error handler
function handleError(error) {
if (error instanceof ValidationError) {
console.log(`Validation error in ${error.field}: ${error.message}`);
} else if (error instanceof NetworkError) {
console.log(`Network error (${error.statusCode}): ${error.message}`);
} else if (error instanceof AuthenticationError) {
console.log('Authentication failed:', error.message);
} else if (error instanceof NotFoundError) {
console.log(`${error.resource} not found`);
} else {
console.log('Unknown error:', error.message);
}
}
try {
throw new NetworkError('Connection failed', 500);
} catch (error) {
handleError(error);
}
console.log();
console.log("=== Error Handling Strategies ===");
// Try-catch-finally
function riskyOperation() {
try {
console.log('Performing risky operation');
// Simulate error
throw new Error('Operation failed');
} catch (error) {
console.error('Error caught:', error.message);
throw error;
} finally {
console.log('Cleanup code executed');
}
}
try {
riskyOperation();
} catch (error) {
console.log('Error re-thrown and caught');
}
// Safe operation with default
function safeDivide(a, b) {
try {
if (b === 0) {
throw new Error('Division by zero');
}
return a / b;
} catch (error) {
console.error('Division error:', error.message);
return null;
}
}
console.log('safeDivide(10, 2):', safeDivide(10, 2));
console.log('safeDivide(10, 0):', safeDivide(10, 0));
// Return error object pattern
function safeOperation() {
try {
let result = 10 / 2;
return { success: true, data: result };
} catch (error) {
return { success: false, error: error.message };
}
}
let result = safeOperation();
console.log('Safe operation result:', result);
console.log();
console.log("=== Async Error Handling ===");
// Async function
async function fetchData(url) {
try {
// Simulate fetch
if (url === 'error') {
throw new NetworkError('Connection failed', 500);
}
return { data: 'success' };
} catch (error) {
console.error('Fetch error:', error.message);
throw error;
}
}
// Handle async errors
async function handleAsync() {
try {
let data = await fetchData('error');
console.log('Data:', data);
} catch (error) {
handleError(error);
}
}
handleAsync();
// Promise error handling
fetchData('error')
.then(data => {
console.log('Data:', data);
})
.catch(error => {
handleError(error);
});
console.log();
console.log("=== Global Error Handling ===");
// Window error handler
window.addEventListener('error', function(event) {
console.error('Global error:', event.error);
console.error('Message:', event.message);
console.error('Source:', event.filename);
console.error('Line:', event.lineno);
// In production, send to error tracking
// sendToErrorTracking({
// message: event.message,
// source: event.filename,
// line: event.lineno,
// error: event.error
// });
});
// Unhandled promise rejection
window.addEventListener('unhandledrejection', function(event) {
console.error('Unhandled promise rejection:', event.reason);
// In production, send to error tracking
// sendToErrorTracking({
// type: 'unhandledRejection',
// reason: event.reason
// });
});
console.log();
console.log("=== Error Handling Utilities ===");
// Error wrapper
function withErrorHandling(fn) {
return function(...args) {
try {
return fn.apply(this, args);
} catch (error) {
console.error(`Error in ${fn.name}:`, error.message);
throw error;
}
};
}
function divide(a, b) {
if (b === 0) {
throw new Error('Division by zero');
}
return a / b;
}
let safeDivide2 = withErrorHandling(divide);
try {
safeDivide2(10, 0);
} catch (error) {
console.log('Error handled by wrapper');
}
// Retry with error handling
async function retryWithErrorHandling(fn, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (i === maxRetries - 1) {
throw error;
}
console.log(`Retry ${i + 1}/${maxRetries}`);
}
}
}
// Error logger
class ErrorLogger {
static log(error, context = {}) {
let errorInfo = {
name: error.name,
message: error.message,
stack: error.stack,
context: context,
timestamp: new Date().toISOString()
};
console.error('Error logged:', errorInfo);
// In production, send to error tracking service
// sendToErrorTracking(errorInfo);
}
}
try {
throw new ValidationError('Invalid input', 'email', 'test');
} catch (error) {
ErrorLogger.log(error, { userId: 123, action: 'login' });
}
Expected Output:
=== Error Handling Practice ===
=== Built-in Error Types ===
Error: Something went wrong
TypeError: TypeError
ReferenceError: ReferenceError
=== Custom Errors ===
Custom error: CustomError Custom error message
Validation error: email = invalid
Network error (500): Connection failed
=== Error Handling Strategies ===
Performing risky operation
Error caught: Operation failed
Cleanup code executed
Error re-thrown and caught
Division error: Division by zero
safeDivide(10, 2): 5
safeDivide(10, 0): null
Safe operation result: { success: true, data: 5 }
=== Async Error Handling ===
Fetch error: Connection failed
Network error (500): Connection failed
Network error (500): Connection failed
=== Global Error Handling ===
[Global handlers registered]
=== Error Handling Utilities ===
Error in divide: Division by zero
Error handled by wrapper
Error logged: { object }
Challenge (Optional):
- Build a complete error handling system
- Create an error tracking service
- Build error recovery mechanisms
- Practice error handling in real applications
Common Mistakes
1. Swallowing Errors
// ❌ Bad: Error swallowed
try {
riskyOperation();
} catch (error) {
// Silent failure
}
// ✅ Good: Handle or log
try {
riskyOperation();
} catch (error) {
console.error('Error:', error);
// Handle appropriately
}
2. Generic Error Messages
// ❌ Bad: Generic
throw new Error('Error');
// ✅ Good: Specific
throw new ValidationError('Email is required', 'email');
3. Not Handling Async Errors
// ❌ Bad: Unhandled promise rejection
async function fetchData() {
let response = await fetch(url);
return response.json();
}
fetchData(); // Error not handled
// ✅ Good: Handle errors
async function fetchData() {
try {
let response = await fetch(url);
return response.json();
} catch (error) {
console.error('Error:', error);
throw error;
}
}
Key Takeaways
- Error Types: Use appropriate built-in or custom error types
- Custom Errors: Create specific error classes for better handling
- Try-Catch: Use try-catch-finally for error handling
- Async Errors: Handle errors in async/await and promises
- Global Handlers: Set up global error handlers
- Best Practices: Be specific, provide context, handle appropriately
- Error Tracking: Log errors for debugging and monitoring
Quiz: Error Handling
Test your understanding with these questions:
-
Error base class:
- A) TypeError
- B) Error
- C) ReferenceError
- D) None
-
Custom errors should:
- A) Extend Error
- B) Not extend anything
- C) Extend Object
- D) Nothing
-
Finally block:
- A) Always executes
- B) Sometimes executes
- C) Never executes
- D) Random
-
Async errors handled with:
- A) try-catch
- B) .catch()
- C) Both
- D) Neither
-
Global error handler:
- A) window.addEventListener('error')
- B) window.onerror
- C) Both
- D) Neither
-
Error handling should:
- A) Be specific
- B) Provide context
- C) Both
- D) Neither
-
Swallowing errors:
- A) Good practice
- B) Bad practice
- C) Sometimes OK
- D) Always OK
Answers:
- B) Error
- A) Extend Error
- A) Always executes
- C) Both
- C) Both
- C) Both
- B) Bad practice
Next Steps
Congratulations! You've learned advanced error handling. You now know:
- Different error types
- How to create custom errors
- Error handling strategies
- Global error handling
What's Next?
- Lesson 20.2: Debugging Techniques
- Learn browser DevTools
- Understand debugging tools
- Master breakpoints and console methods
Additional Resources
- MDN: Error Handling: developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Control_flow_and_error_handling
- MDN: Error: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
Lesson completed! You're ready to move on to the next lesson.
Course Navigation
Functional Programming
Design Patterns
Error Handling and Debugging
- Advanced Error Handling
- Debugging Techniques
Error Handling and Debugging
- Advanced Error Handling
- Debugging Techniques