Loading lesson path
Concept visual
Start at both ends
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 makes debugging easier with proper error context
Common Error Types in Node.js Understanding different error types helps in handling them appropriately:
JSON.parse('{invalid json}');
// TypeError null.someProperty;
// ReferenceError unknownVariable;Formula
// ENOENT: No such file or directory const fs = require('fs');fs.readFile('nonexistent.txt', (err) => {
console.error(err.code); // 'ENOENT'
});Formula
// 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'
});Node.js follows several patterns for error handling:
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);
});Formula
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.)
}
})();For unexpected errors, you can listen for uncaughtException to perform cleanup before exiting:
// 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);Do
Ignore errors (empty catch blocks)
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');
}// ...
}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.