Let's talk about a universal truth in programming - Murphy's law. As you probably already know, it states that anything that can go wrong will eventually go wrong. And let's face it, as much as we try to prevent it, errors and bugs seem to creep into our code at some point.
JavaScript is no exception. This popular programming language is notorious for its average error-handling design. That's why it's crucial to have a good strategy in place to minimize and resolve any issues that may arise.
So, whether you're a seasoned pro or just starting out, don't worry - we've got you covered. In this blog, we'll share some tips and tricks to help you keep your JavaScript code error-free and make sure that your software products succeed. So sit back, grab a coffee, and let's dive in!
1. Use Custom Errors When Handling Operational Exceptions
We introduced custom errors early in this guide to give you an idea of how to customize the error handling to your application’s unique case. It’s advisable to use custom errors wherever possible instead of the generic Error class as it provides more contextual information to the calling environment about the error.
On top of that, custom errors allow you to moderate how an error is displayed to the calling environment. This means that you can choose to hide specific details or display additional information about the error as and when you wish.
You can go so far as to format the error contents according to your needs. This gives you better control over how the error is interpreted and handled.
2. Do Not Swallow Any Exceptions
Even the most senior developers often make a rookie mistake — consuming exceptions levels deep down in their code.
You might come across situations where you have a piece of code that is optional to run. If it works, great; if it doesn’t, you don’t need to do anything about it.
In these cases, it’s often tempting to put this code in a try block and attach an empty catch block to it. However, by doing this, you’ll leave that piece of code open to causing any kind of error and getting away with it. This can become dangerous if you have a large codebase and many instances of such poor error management constructs.
The best way to handle exceptions is to determine a level on which all of them will be dealt and raise them until there. This level can be a controller (in an MVC architecture app) or a middleware (in a traditional server-oriented app).
This way, you’ll get to know where you can find all the errors occurring in your app and choose how to resolve them, even if it means not doing anything about them.
3. Use a Centralized Strategy for Logs and Error Alerts
Logging an error is often an integral part of handling it. Those who fail to develop a centralized strategy for logging errors may miss out on valuable information about their app’s usage.
An app’s event logs can help you figure out crucial data about errors and help to debug them quickly. If you have proper alerting mechanisms set up in your app, you can know when an error occurs in your app before it reaches a large section of your user base.
It’s advisable to use a pre-built logger or create one to suit your needs. You can configure this logger to handle errors based on their levels (warning, debug, info, etc.), and some loggers even go so far as to send logs to remote logging servers immediately. This way, you can watch how your application’s logic performs with active users.
4. Notify Users About Errors Appropriately
Another good point to keep in mind while defining your error handling strategy is to keep the user in mind.
All errors that interfere with the normal functioning of your app must present a visible alert to the user to notify them that something went wrong so the user can try to work out a solution. If you know a quick fix for the error, such as retrying an operation or logging out and logging back in, make sure to mention it in the alert to help fix the user experience in real-time.
In the case of errors that don’t cause any interference with the everyday user experience, you can consider suppressing the alert and logging the error to a remote server for resolving later.
5. Implement a Middleware (Node.js)
The Node.js environment supports middlewares to add functionalities to server applications. You can use this feature to create an error-handling middleware for your server.
The most significant benefit of using middleware is that all of your errors are handled centrally in one place. You can choose to enable/disable this setup for testing purposes easily.
Here’s how you can create a basic middleware:
const logError = err => {
console.log("ERROR: " + String(err))
}
const errorLoggerMiddleware = (err, req, res, next) => {
logError(err)
next(err)
}
const returnErrorMiddleware = (err, req, res, next) => {
res.status(err.statusCode || 500)
.send(err.message)
}
module.exports = {
logError,
errorLoggerMiddleware,
returnErrorMiddleware
}
You can then use this middleware in your app like this:
const { errorLoggerMiddleware, returnErrorMiddleware } = require('./errorMiddleware')
app.use(errorLoggerMiddleware)
app.use(returnErrorMiddleware)
You can now define custom logic inside the middleware to handle errors appropriately. You don’t need to worry about implementing individual error handling constructs throughout your codebase anymore.
6. Restart Your App To Handle Programmer Errors (Node.js)
When Node.js apps encounter programmer errors, they might not necessarily throw an exception and try to close the app. Such errors can include issues arising from programmer mistakes, like high CPU consumption, memory bloating, or memory leaks. The best way to handle these is to gracefully restart the app by crashing it via the Node.js cluster mode or a unique tool like PM2. This can ensure that the app doesn’t crash upon user action, presenting a terrible user experience.
7. Catch All Uncaught Exceptions (Node.js)
You can never be sure that you have covered every possible error that can occur in your app. Therefore, it’s essential to implement a fallback strategy to catch all uncaught exceptions from your app.
Here’s how you can do that:
process.on('uncaughtException', error => {
console.log("ERROR: " + String(error))
// other handling mechanisms
})
You can also identify if the error that occurred is a standard exception or a custom operational error. Based on the result, you can exit the process and restart it to avoid unexpected behavior.
8. Catch All Unhandled Promise Rejections (Node.js)
Similar to how you can never cover for all possible exceptions, there’s a high chance that you might miss out on handling all possible promise rejections. However, unlike exceptions, promise rejections don’t throw errors.
So, an important promise that was rejected might slip by as a warning and leave your app open to the possibility of running into unexpected behavior. Therefore, it’s crucial to implement a fallback mechanism for handling promise rejection.
Here’s how you can do that:
const promiseRejectionCallback = error => {
console.log("PROMISE REJECTED: " + String(error))
}
process.on('unhandledRejection', callback)
Summary
Keeping your JavaScript code error-free is crucial for the success of your software products. With these tips and tricks, you achieve just that while ensuring any issues that may arise can be handled efficiently and effectively.
Take your coding skills to the next level with our comprehensive JavaScript tutorials: https://kinsta.com/topic/javascript-tutorials/
About Kinsta Hosting
Tired of slow web projects and inefficient management? Kinsta’s cloud platform is designed to help companies and dev teams work smarter. Host your modern web applications all in one place and get back to what you love: coding.
Want to try it out for yourself? Test our Application Hosting or Database Hosting for free right here: https://kinsta.com/application-hosting/.