JavaScript Error Handling
Error handling allows your program to deal with unexpected situations gracefully instead of crashing. JavaScript provides try...catch...finally for handling errors, and the throw statement for creating custom errors.
try...catch
The try block contains code that might throw an error. The catch block runs only if an error occurs, receiving the error object as a parameter.
// Catching an error
try {
const data = JSON.parse('invalid json');
} catch (error) {
console.log(error.name); // 'SyntaxError'
console.log(error.message); // 'Unexpected token...'
}
console.log('Code continues after catch');
// Without try...catch, the error would crash the program
// With try...catch, we handle it gracefully
// catch binding is optional (ES2019+)
try {
JSON.parse('{}');
console.log('Valid JSON parsed successfully');
} catch {
console.log('Failed to parse');
}The finally Block
The finally block executes regardless of whether an error occurred or not. It is commonly used for cleanup operations like closing connections or releasing resources.
function readData() {
console.log('Opening resource...');
try {
console.log('Reading data...');
throw new Error('Read failed');
} catch (error) {
console.log('Error: ' + error.message);
} finally {
console.log('Closing resource (always runs)');
}
}
readData();
// Opening resource...
// Reading data...
// Error: Read failed
// Closing resource (always runs)
// finally runs even with return statements
function test() {
try {
return 'try';
} finally {
console.log('finally runs before return');
}
}
console.log(test());
// finally runs before return
// 'try'The throw Statement
The throw statement lets you create custom errors. You can throw any value, but it is best practice to throw Error objects so they include a stack trace.
// Throw a built-in Error
function divide(a, b) {
if (b === 0) {
throw new Error('Division by zero');
}
return a / b;
}
try {
console.log(divide(10, 2)); // 5
console.log(divide(10, 0)); // throws!
} catch (e) {
console.log(e.message); // 'Division by zero'
}
// Throw specific error types
function setAge(age) {
if (typeof age !== 'number') {
throw new TypeError('Age must be a number');
}
if (age < 0 || age > 150) {
throw new RangeError('Age must be between 0 and 150');
}
console.log('Age set to ' + age);
}
try {
setAge('old');
} catch (e) {
console.log(e.name + ': ' + e.message);
// TypeError: Age must be a number
}Custom Error Classes
You can create custom error classes by extending the built-in Error class. This lets you create application-specific error types that can be caught selectively.
class ValidationError extends Error {
constructor(field, message) {
super(message);
this.name = 'ValidationError';
this.field = field;
}
}
class NotFoundError extends Error {
constructor(resource) {
super(`${resource} not found`);
this.name = 'NotFoundError';
this.resource = resource;
}
}
function validateUser(user) {
if (!user.name) {
throw new ValidationError('name', 'Name is required');
}
if (!user.email) {
throw new ValidationError('email', 'Email is required');
}
}
try {
validateUser({ name: 'Alice' });
} catch (e) {
if (e instanceof ValidationError) {
console.log(`${e.name}: ${e.field} - ${e.message}`);
// ValidationError: email - Email is required
} else {
throw e; // Re-throw unknown errors
}
}Error Propagation
When an error is thrown and not caught, it propagates up the call stack until it finds a catch block or crashes the program. You can catch errors at different levels and re-throw them if needed.
function level3() {
throw new Error('Error in level 3');
}
function level2() {
level3(); // Error propagates up
}
function level1() {
try {
level2();
} catch (e) {
console.log('Caught at level 1: ' + e.message);
}
}
level1(); // 'Caught at level 1: Error in level 3'
// Re-throwing: catch only what you can handle
function processData(data) {
try {
JSON.parse(data);
} catch (e) {
if (e instanceof SyntaxError) {
console.log('Invalid JSON: ' + e.message);
} else {
throw e; // Re-throw unexpected errors
}
}
}
processData('not json'); // 'Invalid JSON: ...'Async Error Handling Basics
Errors in asynchronous code (Promises, async/await) require special handling. Use .catch() for Promises or try...catch inside async functions.
// Promise .catch()
function fetchData(shouldFail) {
return new Promise((resolve, reject) => {
if (shouldFail) {
reject(new Error('Fetch failed'));
} else {
resolve('data');
}
});
}
fetchData(true)
.then(data => console.log(data))
.catch(e => console.log('Promise error: ' + e.message));
// 'Promise error: Fetch failed'
// async/await with try...catch
async function getData() {
try {
const result = await fetchData(true);
console.log(result);
} catch (e) {
console.log('Async error: ' + e.message);
}
}
getData(); // 'Async error: Fetch failed'| Concept | Use Case |
|---|---|
| try...catch | Handle errors that might occur in synchronous code |
| finally | Cleanup operations that must always run |
| throw | Create and signal an error condition |
| Custom errors | Application-specific error types with extra info |
| Re-throwing | Catch only errors you can handle, pass others up |
| .catch() | Handle errors in Promise chains |
| async try...catch | Handle errors in async/await code |