Error Handling in JavaScript
Error handling is a critical aspect of writing robust JavaScript applications. Properly managing errors helps you create more reliable code, improve debugging, and provide better user experiences when things go wrong.
Types of Errors in JavaScript
JavaScript has several built-in error types that you might encounter:
- SyntaxError: Occurs when there's a mistake in your code syntax
- ReferenceError: Occurs when referencing a variable that doesn't exist
- TypeError: Occurs when a value is not of the expected type
- RangeError: Occurs when a value is not in the expected range
- URIError: Occurs when using URI handling functions incorrectly
- EvalError: Occurs when using the eval() function incorrectly
// SyntaxError
if (true { // Missing closing parenthesis
console.log("This will cause a syntax error");
}
// ReferenceError
console.log(undefinedVariable); // Variable doesn't exist
// TypeError
const num = 123;
num.toUpperCase(); // Numbers don't have toUpperCase method
// RangeError
const arr = new Array(-1); // Cannot create array with negative length
Try...Catch Statement
The try...catch statement is the primary way to handle errors in JavaScript. It allows you to "try" a block of code and "catch" any errors that occur.
try {
// Code that might throw an error
const data = JSON.parse('{"name": "John"}');
console.log(data.name); // John
} catch (error) {
// Code to handle the error
console.error('An error occurred:', error.message);
} finally {
// Code that will run regardless of whether an error occurred
console.log('This will always execute');
}
Note: The finally
block is optional and will execute regardless of whether an error was thrown or caught.
Catching Specific Error Types
You can check the error type in the catch block to handle different errors differently:
try {
// Code that might throw different types of errors
const value = JSON.parse(userInput);
} catch (error) {
if (error instanceof SyntaxError) {
console.error('Invalid JSON syntax:', error.message);
} else if (error instanceof TypeError) {
console.error('Type error:', error.message);
} else {
console.error('Unknown error:', error.message);
}
}
Throwing Custom Errors
You can throw your own errors using the throw
statement. This is useful for creating custom error conditions in your code.
function divide(a, b) {
if (b === 0) {
throw new Error('Division by zero is not allowed');
}
return a / b;
}
try {
const result = divide(10, 0);
console.log(result);
} catch (error) {
console.error(error.message); // "Division by zero is not allowed"
}
Creating Custom Error Types
You can create your own error types by extending the built-in Error class:
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = 'ValidationError';
}
}
class DatabaseError extends Error {
constructor(message) {
super(message);
this.name = 'DatabaseError';
}
}
// Using custom errors
function fetchUser(id) {
if (!id) {
throw new ValidationError('User ID is required');
}
// Simulate database error
if (id < 0) {
throw new DatabaseError('Database connection failed');
}
return { id, name: 'John Doe' };
}
try {
const user = fetchUser(-1);
} catch (error) {
if (error instanceof ValidationError) {
console.error('Validation failed:', error.message);
} else if (error instanceof DatabaseError) {
console.error('Database error:', error.message);
// Retry or use fallback
} else {
console.error('Unknown error:', error.message);
}
}
Error Handling in Asynchronous Code
Promises
With Promises, you can use the .catch()
method to handle errors:
fetch('https://api.example.com/data')
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
})
.then(data => console.log('Data:', data))
.catch(error => console.error('Fetch error:', error.message));
Async/Await
With async/await, you can use try...catch blocks to handle errors in asynchronous code:
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const data = await response.json();
console.log('Data:', data);
return data;
} catch (error) {
console.error('Fetch error:', error.message);
// You might want to return a default value or re-throw the error
return null;
}
}
Best Practice: Always handle errors in your async functions rather than letting them propagate up the call stack unexpectedly.
Error Handling Strategies
Defensive Programming
Anticipate potential errors and handle them before they occur:
function getUserName(user) {
// Defensive approach
if (!user) return 'Guest';
if (typeof user !== 'object') return 'Unknown';
if (!user.name) return 'Anonymous';
return user.name;
}
// This function is robust against various inputs
console.log(getUserName(null)); // "Guest"
console.log(getUserName(123)); // "Unknown"
console.log(getUserName({})); // "Anonymous"
console.log(getUserName({ name: 'Alice' })); // "Alice"
Graceful Degradation
Provide fallback behavior when errors occur:
function loadUserPreferences() {
try {
// Try to load from localStorage
const preferences = JSON.parse(localStorage.getItem('userPrefs'));
return preferences || getDefaultPreferences();
} catch (error) {
console.warn('Could not load preferences:', error.message);
// Fallback to defaults
return getDefaultPreferences();
}
}
function getDefaultPreferences() {
return {
theme: 'light',
fontSize: 'medium',
notifications: true
};
}
Interactive Example
Try out this interactive example to see error handling in action:
Best Practices for Error Handling
- Be specific: Catch specific errors rather than all errors when possible
- Provide context: Include useful information in error messages
- Log errors: Log errors for debugging but avoid exposing sensitive details to users
- Fail gracefully: Provide fallback behavior when errors occur
- Don't swallow errors: Avoid empty catch blocks that hide errors
- Clean up resources: Use finally blocks to ensure resources are properly released
Warning: Never use try...catch as a way to control normal program flow. It should be reserved for exceptional conditions.
Next Steps
Now that you understand error handling in JavaScript, you can explore related topics:
- Debugging techniques in JavaScript
- Error monitoring and reporting in production
- Unit testing and error scenarios
- Error boundaries in React applications