Error Handling in JavaScript
Built-in Error Types
JavaScript provides several built-in error types:
- Error: Base error type
- SyntaxError: Invalid syntax
- ReferenceError: Reference to an undefined variable
- TypeError: Operation on an inappropriate type
- RangeError: Numeric value outside of valid range
- URIError: Incorrect use of URI functions
- EvalError: Error in the eval() function
Try-Catch-Finally
The primary mechanism for handling errors in JavaScript:
try {
// Code that might throw an error
const result = riskyOperation();
} catch (error) {
// Handle the error
console.error('An error occurred:', error.message);
} finally {
// Code that always runs, regardless of error
cleanupResources();
}
Creating Custom Errors
Extend the Error class to create custom error types:
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = 'ValidationError';
this.statusCode = 400;
}
}
try {
if (!isValid(data)) {
throw new ValidationError('Invalid data provided');
}
} catch (error) {
if (error instanceof ValidationError) {
// Handle validation errors
} else {
// Handle other errors
}
}
Error Properties
Common properties available on error objects:
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
}
Async Error Handling
With Promises
fetchData()
.then(data => {
// Process data
})
.catch(error => {
// Handle errors from fetchData or any previous .then
console.error('Error:', error.message);
})
.finally(() => {
// Cleanup code
});
With Async/Await
async function getData() {
try {
const data = await fetchData();
return processData(data);
} catch (error) {
console.error('Error:', error.message);
// Handle error or rethrow
throw error;
} finally {
// Cleanup code
}
}
Global Error Handling
In Browser
// Catch unhandled errors
window.addEventListener('error', (event) => {
console.error('Unhandled error:', event.error);
// Prevent default browser error handling
event.preventDefault();
});
// Catch unhandled promise rejections
window.addEventListener('unhandledrejection', (event) => {
console.error('Unhandled promise rejection:', event.reason);
// Prevent default handling
event.preventDefault();
});
In Node.js
process.on('uncaughtException', (error) => {
console.error('Uncaught exception:', error);
// Perform cleanup
process.exit(1); // Exit with failure code
});
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled promise rejection:', reason);
// Handle the error or exit
});
Error Handling Patterns
Error Boundaries (React)
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
logErrorToService(error, errorInfo);
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
// Usage
<ErrorBoundary>
<MyComponent />
</ErrorBoundary>
Retry Pattern
async function fetchWithRetry(url, options = {}, retries = 3, backoff = 300) {
try {
return await fetch(url, options);
} catch (error) {
if (retries <= 0) {
throw error;
}
await new Promise(resolve => setTimeout(resolve, backoff));
return fetchWithRetry(url, options, retries - 1, backoff * 2);
}
}
Error Middleware (Express.js)
app.get('/api/data', (req, res, next) => {
try {
// Operation that might fail
if (!req.user) {
throw new Error('User not authenticated');
}
res.json({ data: 'success' });
} catch (error) {
next(error); // Pass to error middleware
}
});
// Error middleware
app.use((error, req, res, next) => {
console.error(error);
res.status(500).json({
error: {
message: error.message || 'Internal Server Error'
}
});
});
Best Practices
- Be specific: Catch only the errors you can handle
- Don’t swallow errors: Always log or handle errors appropriately
- Use finally for cleanup: Ensure resources are released
- Centralize error handling: Use global handlers or middleware
- Include context: Add relevant information to error messages
- Fail fast: Detect and report errors as early as possible
- Validate inputs: Prevent errors by validating data before processing
Interview Tips
- Explain the difference between throwing and handling errors
- Describe how error propagation works in asynchronous code
- Discuss strategies for debugging errors in production
- Demonstrate knowledge of error handling patterns in frameworks
- Explain how to create and use custom error types effectively
Test Your Knowledge
Take a quick quiz to test your understanding of this topic.