JavaScript Exception Handling: Patterns & Best Practices

Learn how to handle JavaScript Exceptions the right way

INDRAJITH EKANAYAKE
Bits and Pieces

--

Over the past few years, JavaScript has emerged as a leading programing language for web application development. With its wide adoption, having the focus on robust development practices has become a necessity. And, one such fundamental practice is to deal with program exceptions.

So, in this article, I will discuss different patterns and best practices around Exception Handling with JavaScript.

1. Don’t Overuse the “Try-Catch”

The first best practice of “Exception Handling” is, don’t overuse “Exception Handling.”

Typically, we handle exceptions at the outer layers and throws from the inner so that once an exception occurs, we can better understand what leads to it.

However, one of the common mistakes developers make is to overuse exception handling. Sometimes we do it to keep the code look consistent across different files and methods. But, unfortunately, these would cause adverse effects both for application performance and error detection.

So instead, only focus on the possible troublemakers in code, where exception handling will help to improve robustness and increase the chance of detecting the error.

2. Avoid Browser Specific Non-Standard Methods

Though many browsers adhere to a common standard, some browser-specific JavaScript implementations fail on others. For example, the below syntax only works with Firefox.

catch(e if e instanceof MyError)
{
console.error(e.name + ': ' + e.message);
...
}

I hope known one wants an exception within the exception handling syntax😀

Therefore, it is always good to use cross-browser-friendly JavaScript code whenever possible, especially when dealing with exceptions.

3. Exception Handling in JavaScript Asynchronous Code

Consider the below code snippet, which is a simple async function that throws an error.

async function test()
{
throw new error();
}

Suppose you call this method as shown below within a try-catch block.

try
{
test();
}
catch(err)
{
// This catch block is not reached
}
finally
{
// This will be executed before the promise is rejected
}

Since the function is asynchronous, a promise will be returned when calling the test() function. However, since the try-catch block won’t wait for the function execution, the catch block won’t execute even if the function causes an error. Because of that, the promise is rejected when the finally block is executed.

Therefore, it is not possible to use try-catch with asynchronous functions because it always returns a rejecting promise.

Take special care when handling exceptions in asynchronous code executions.

So, it is essential to use await with async functions as follows to avoid causing this issue.

async function testing()
{
try
{
await test();
}
catch(err)
{
// This block is reached after the test function is tried
}
finally
{
// At the end, this will be executed
}
}
testing();

Tip: Build with independent components, for speed and scale

Instead of building monolithic apps, build independent components first and compose them into features and applications. It makes development faster and helps teams build more consistent and scalable applications.

OSS Tools like Bit offer a great developer experience for building independent components and composing applications. Many teams start by building their Design Systems or Micro Frontends, through independent components.
Give it a try →

An independently source-controlled and shared “card” component. On the right => its dependency graph, auto-generated by Bit.

4. Avoid using Exceptions for Flow Control

In a program, the control flow is basically how a set of code instructions, executed. However, controlling the program flow by using exceptions is highly discouraged due to a couple of reasons.

One such issue is that using exceptions makes it challenging to follow the code execution. This is because exceptions work as jump statements that move from one place to another without following a sequence. Therefore, it affects the predictability of the code as well.

Using exceptions as a control flow breaks “The Principle of Least Astonishment” as well.

Besides, as the name “exception” implies, these are for exceptional situations. Furthermore, the try-catch block is an expensive operation. The performance impact becomes further visible if you use to throw exceptions to control the flow. Though the actual performance impact is browser-dependent, it is safe to avoid using exceptions to control the program flow.

It also makes it difficult to Unit Test since we need to let the exception occur to test the code. That makes it harder to simulate the environment, causing this difficulty.

5. Implement Remote Exception Logging

Typically we don’t expect exceptions to occur. But, they never stop surprising us. So, when an exception occurs, we should always get notified to understand what went wrong. And that’s where exception logs come in.

However, we face a new challenge with JavaScript where the code is executed in the users’ browser. Therefore, we need a mechanism to track these errors in the client browser and send them to a server for analysis.

Gladly there are many tools out there that do it for us. So, one of the best practices is to integrate one of these tools into your application. Below, articles share some of these tools.

Besides, if you plan to implement your own custom logic, you can use the window.onerror handle to catch exceptions and send them to a server using JavaScript.

window.onerror = function(message, url, lineNumber) {  
//Send error to server
return true;
};

But you might wonder, why can’t the browser send the exception information to a pre-configured server URL?

There is an existing implementation for non-program exceptions like Content Security Policy Violation Report. We can set a server URL as a parameter for the browser to POST it. However, for general exception handling, the feature support still isn’t there.

Final Thoughts

As I mentioned at the beginning of the article, exception handling helps greatly when the code faces runtime errors. And, handling exceptions properly will prevent the code from sudden termination and provide a human-readable context as to what causes the problem.

But their use is expensive. So we have to make sure we use exceptions only in places that make more sense. That will make the code work more smoothly and efficiently.

Thanks for reading!!!

--

--