Flash cards
Review the key moves
What is the main idea behind Node.js Error Handling?
Lesson checks
Practice each idea before moving on
Short Mimo-style checks built from this lesson's code, terms, and sequence.
Which statement best captures the main point of this lesson?
Complete the missing token from the example code.
// ___Put the learning moves in the order that makes the concept easiest to apply.
Why Handle Errors?
Errors are inevitable in any program, but how you handle them makes all the difference. In Node.js, proper error handling is crucial because:
- It prevents applications from crashing unexpectedly
- It provides meaningful feedback to users
- It makes debugging easier with proper error context
- It helps maintain application stability in production
- It ensures resources are properly cleaned up
Common Error Types in Node.js
Understanding different error types helps in handling them appropriately:
Standard JavaScript Errors
// SyntaxError
JSON.parse('{invalid json}');
// TypeError
null.someProperty;
// ReferenceError
unknownVariable;System Errors
// ENOENT: No such file or directory
const fs = require('fs');
fs.readFile('nonexistent.txt', (err) => {
console.error(err.code); // 'ENOENT'
});
// ECONNREFUSED: Connection refused
const http = require('http');
const req = http.get('http://nonexistent-site.com', (res) => {});
req.on('error', (err) => {
console.error(err.code); // 'ECONNREFUSED' or 'ENOTFOUND'
});Basic Error Handling
Node.js follows several patterns for error handling:
Error-First Callbacks
The most common pattern in Node.js core modules where the first argument to a callback is an error object (if any occurred).
Example: Error-First Callback
const fs = require('fs');
function readConfigFile(filename, callback) {
fs.readFile(filename, 'utf8', (err, data) => {
if (err) {
// Handle specific error types
if (err.code === 'ENOENT') {
return callback(new Error(`Config file ${filename} not found`));
} else if (err.code === 'EACCES') {
return callback(new Error(`No permission to read ${filename}`));
}
// For all other errors
return callback(err);
}
// Process data if no error
try {
const config = JSON.parse(data);
callback(null, config);
} catch (parseError) {
callback(new Error(`Invalid JSON in ${filename}`));
}
});
}
// Usage
readConfigFile('config.json', (err, config) => {
if (err) {
console.error('Failed to read config:', err.message);
// Handle the error (e.g., use default config)
return;
}
console.log('Config loaded successfully:', config);
});Using try...catch with Async/Await
With async/await, you can use try/catch blocks for both synchronous and asynchronous code:
Example: try/catch with Async/Await
const fs = require('fs').promises;
async function loadUserData(userId) {
try {
const data = await fs.readFile(`users/${userId}.json`, 'utf8');
const user = JSON.parse(data);
if (!user.email) {
throw new Error('Invalid user data: missing email');
}
return user;
} catch (error) {
// Handle different error types
if (error.code === 'ENOENT') {
throw new Error(`User ${userId} not found`);
} else if (error instanceof SyntaxError) {
throw new Error('Invalid user data format');
}
// Re-throw other errors
throw error;
} finally {
// Cleanup code that runs whether successful or not
console.log(`Finished processing user ${userId}`);
}
}
// Usage
(async () => {
try {
const user = await loadUserData(123);
console.log('User loaded:', user);
} catch (error) {
console.error('Failed to load user:', error.message);
// Handle error (e.g., show to user, retry, etc.)
}
})();Uncaught Exceptions
For unexpected errors, you can listen for uncaughtException to perform cleanup before exiting:
Example: Global Error Handlers
// Handle uncaught exceptions (synchronous errors)
process.on('uncaughtException', (error) => {
console.error('UNCAUGHT EXCEPTION! Shutting down...');
console.error(error.name, error.message);
// Perform cleanup (close database connections, etc.)
server.close(() => {
console.log('Process terminated due to uncaught exception');
process.exit(1); // Exit with failure
});
});
// Handle unhandled promise rejections
process.on('unhandledRejection', (reason, promise) => {
console.error('UNHANDLED REJECTION! Shutting down...');
console.error('Unhandled Rejection at:', promise, 'Reason:', reason);
// Close server and exit
server.close(() => {
process.exit(1);
});
});
// Example of an unhandled promise rejection
Promise.reject(new Error('Something went wrong'));
// Example of an uncaught exception
setTimeout(() => {
throw new Error('Uncaught exception after timeout');
}, 1000);Dos and Don'ts
- Handle errors at the appropriate level
- Log errors with sufficient context
- Use custom error types for different scenarios
- Clean up resources in finally blocks
- Validate input to catch errors early
- Ignore errors (empty catch blocks)
- Expose sensitive error details to clients
- Use try/catch for flow control
- Swallow errors without logging them
- Continue execution after unrecoverable errors
Custom Error Types
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = 'ValidationError';
this.field = field;
this.statusCode = 400;
}
}
class NotFoundError extends Error {
constructor(resource) {
super(`${resource} not found`);
this.name = 'NotFoundError';
this.statusCode = 404;
}
}
// Usage
function getUser(id) {
if (!id) {
throw new ValidationError('User ID is required', 'id');
}
// ...
}Summary
Effective error handling is a critical aspect of building robust Node.js applications.
By understanding different error types, using appropriate patterns, and following best practices, you can create applications that are more stable, maintainable, and user-friendly.
Remember that good error handling is not just about preventing crashes-it's about providing meaningful feedback, maintaining data integrity, and ensuring a good user experience even when things go wrong.