Error handling in JavaScript

Tram Ho

When programming, we are often afraid of errors. But by working with it, we learn why one should not do this (and the other), as well as how to make the code better.

This article is divided into 3 parts, the first is an overview of the error. We will then focus on the backend (Node.js + Express.js) and finally the error handling in React.js.

I. JavaScript Errors and generic handling

throw new Error('something went wrong') – will create an instance of Error in JavaScript and stop executing the code until you do something with the Error. When you're first programming JavaScript, you usually won't do it, but only encounter it when using the library (or when executing code), such as ReferenceError: fs is not defined .

Error Object

Object Error has 2 properties that we can use. One is the message, that is, the argument you pass to the Error constructor, for example new Error('This is the message') . You can access messages through the messsage property:

The second extremely important attribute is the stack trace of the Error. You can access it via the stack property. The stack will show you the history of the files responsible for the error. The stack also contains messages and is followed by the corresponding error and file.

Throwing and Handling Errors

Because the Error instance does not have any effects, such as new Error ('…') doesn't do anything. When Error throw n, it becomes more interesting. The code will stop executing until you handle it somewhere. Remember whether you throw error manually or the library or runtime. Let's see how we can work with errors in different scenarios.

try ... catch This is the simplest way, but is often overlooked when dealing with errors – fortunately it is now used more often thanks to async / await. It is also used to catch sync errors. For example:

If we do not put console.log(b) in try ... catch , the code will stop executing.

… finally Sometimes we need to execute the code no matter what the condition occurs, now you can use the third block as finally , just add a line after try ... catch .

Asynchronous handling – Callbacks

Asynchronous is the subject that you always have to pay attention when working with JavaScript. When you have an asynchronous function, and an error occurs in that function, the code will continue to execute, so no errors will be fired immediately. When handling asynchronous functions with callbacks, you usually get two arguments in the callback, usually like this:

If there is an error, the err parameter will be equivalent to the error. Otherwise, this parameter will be undefined or null .

Asynchronous – Promises

A better way to handle asynchronous is to use promises. Besides making the code easier to read, we also improve error handling. No need to worry too much about catching the exact Error, as long as there is a catch block. When chaining a promise, each block catch will catch all the errors from executing the promise or from the last block catch . Note that promises without catch will not be able to terminate code, but you will get a more difficult message like this:

So always add a catch block for promises. As follows:

try … catch – again

With the introduction of async / await in JavaScript, we return to the classic way of catching errors, with try ... catch ... finally , quite gently:

II. Error handling in the Server

Now that we have the tools to work with the Error, let's look at how we will actually do it. Error handling in the backend is part of your application. There are many approaches to this problem, I will show you how to approach custom Error constructor and Error codes, which we can easily pass to the frontend or API.

We will use the Express.js framework as routing. Think about the structure we want to catch the most effectively. We want:

  1. General error handling, for example: Something went wrong, please try again or contact us. . This approach is not so good, but at least it notifies the user that something is wrong, instead of infinite loading screen.
  2. Specific error handling to give users detailed information about the error and how to handle it, such as some missing information, imported content already in the database, …

Building custom Error constructor

We will use the Error constructor and extend it. Inheritance is a risky alternative in JavaScript, but this time it's very useful. Why do we need it? Because we still want to stack trace to debug more efficiently. We just need to add the code , which we will access later via err.code , as well as the status to pass to the frontend.

Process routing

As mentioned, we want a single point of truth to handle errors, which means every route has the same error handling. By default express is not supported because the route is packaged.

To solve this problem, we can install a route handler and define the logic as a regular function. That way, when the routing function throw error, it will be returned to the route handler, then passed to the frontend. Whenever an error occurs in the backend, we want to pass the response to the frontend, assuming it is a JSON API in the following format:

The route handler looks like this:

Let's see what the routing file will look like:

Here I don't handle the request, I just added some error scenarios. Basically you will have an unresolved error, the frontend will get:

Or you will throw a CustomError :

will become

III. Show error to the user

The final step is to manage the errors in the frontend. You want to handle errors in both the frontend as well as the backend. First let's see how we display the error. As connected, I will use React to illustrate.

Save Errors in React state

Errors and messages can change, so you need to put them in component state. By default, the error will be reset so the first time the user view will not see the error.

Next, we need to distinguish the different types of errors. There are 3 types:

  1. Global Error, for example generic error in backend or non-logged in user …
  2. Specific errors from backend, such as incorrect password …
  3. Specific errors from the frontend, such as input validation errors that have not been entered

Global Errors

Usually I save these errors in the parent component and render static UI.

As you can see, we have an error in the state. We also have a method to reset and change the value of the error. We pass the value and reset it into the GlobalError component, which will display and reset the error when the user clicks the x:

In line 5, we do not render because there is no error. You can now use the global error state anywhere, such as when a request from the backend returns an error: 'GENERIC' :

Handling specific errors

Similar to the global error, we have local error state in the component.

Error internationalization with error code

You may wonder what error codes like GENERIC do. As the application grows, you may need to support multiple languages, then returning messages based on the user's language will be simpler when matching error code.

Hopefully you've got a new perspective on error handling. console.error(err) has become a thing of the past. It is quite convenient to debug but takes time to build for production, it is best to use a logging library.

Reference: https://levelup.gitconnected.com/the-definite-guide-to-handling-errors-gracefully-in-javascript-58424d9c60e6

Share the news now

Source : Viblo