A key component of creating dependable and durable Node.js apps is error handling. When errors are handled correctly, applications can avoid crashes, gracefully recover from unforeseen circumstances, and give users informative error messages. Using real-world TypeScript examples, we will examine popular error handling techniques and recommended practices in Node.js in this post.
Comprehending Node.js Errors
Errors in Node.js can happen at many levels:
Errors that arise when synchronous code is being executed are referred to as synchronous errors.
Asynchronous errors: Errors that arise during asynchronous processes, like async/await, promises, and callbacks.
Operational errors: Fixable mistakes like bad user input or network outages.
Programmer errors: Logic mistakes or undefined variables are examples of bugs in the code that are typically not fixable.
Synchronous Error Handling
Synchronous errors can be handled using try-catch blocks. Here's an example in TypeScript:
function divide(a: number, b: number): number {
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("An error occurred:", error.message);
}
Asynchronous Error Handling
Handling errors in asynchronous code can be more challenging. Let's look at different approaches:
1. Callbacks
In callback-based code, errors are usually passed as the first argument to the callback function.
import * as fs from 'fs';
fs.readFile('path/to/file', 'utf8', (err, data) => {
if (err) {
console.error("An error occurred:", err.message);
return;
}
console.log(data);
});
2. Promises
Promises provide a cleaner way to handle asynchronous errors.
import { readFile } from 'fs/promises';
readFile('path/to/file', 'utf8')
.then(data => {
console.log(data);
})
.catch(err => {
console.error("An error occurred:", err.message);
});
3. Async/Await
Async/await syntax allows for more readable and maintainable asynchronous code.
import { readFile } from 'fs/promises';
async function readFileAsync() {
try {
const data = await readFile('path/to/file', 'utf8');
console.log(data);
} catch (err) {
console.error("An error occurred:", err.message);
}
}
readFileAsync();
Centralized Error Handling
Centralized error handling can help manage errors consistently across your application. In an Express.js application, you can use middleware to handle errors.
1. Express Error Handling Middleware
import express, { Request, Response, NextFunction } from 'express';
const app = express();
app.get('/', (req: Request, res: Response) => {
throw new Error("Something went wrong!");
});
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
console.error(err.stack);
res.status(500).send({ message: err.message });
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
Graceful Shutdown
Handling errors that can cause your application to crash is important. Implementing a graceful shutdown ensures that your application cleans up resources and completes pending operations before exiting.
import http from 'http';
import express from 'express';
const app = express();
const server = http.createServer(app);
process.on('uncaughtException', (err) => {
console.error('Uncaught Exception:', err.message);
shutdown();
});
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection:', reason);
shutdown();
});
function shutdown() {
server.close(() => {
console.log('Server closed');
process.exit(1);
});
setTimeout(() => {
console.error('Forced shutdown');
process.exit(1);
}, 10000);
}
app.get('/', (req, res) => {
throw new Error("Unexpected error");
});
server.listen(3000, () => {
console.log('Server is running on port 3000');
});
Best Practices
Always handle errors: Never ignore errors. Always handle them appropriately.
Use error boundaries in React: If using React with Node.js, use error boundaries to catch errors in the component tree.
Log errors: Implement logging to capture detailed error information for debugging and monitoring.
Categorize errors: Differentiate between operational errors and programmer errors and handle them accordingly.
Fail gracefully: Design your application to fail gracefully, providing meaningful feedback to users and maintaining functionality where possible.
Conclusion
Handling errors well is essential to creating reliable Node.js apps. Adopting best practices and utilizing suitable patterns for both synchronous and asynchronous error handling will help you make sure that your application can handle mistakes gracefully and offer users an improved experience. Adding graceful shutdown and centralized error handling improves your application's dependability even more.
Adopt these techniques to create robust Node.js applications that can manage failures well and uphold strict reliability and user experience guidelines.
Top comments (0)