Handling Errors in JavaScript: The Definitive Guide

Lukas Gisder-Dubé
Level Up Coding
Published in
12 min readNov 13, 2018

--

Photo by rawpixel on Unsplash

Following up on my last article, I want to talk about Errors. Errors are good — I’m sure you’ve heard that before. At first sight we fear Errors, because they often include being hurt or feeling humiliated in public. By doing Errors, we actually learn how not to do something and how to do it better next time.

This obviously is about learning from Errors (mistakes) in real life. Errors in programming are a bit different. They provide us with nice features to improve our code and tell the user when something is wrong (and maybe also to educate them on how to fix it).

This article will be structured in 3 parts, first we will have a look at Errors in general. After that we will focus on the backend (Node.js + Express.js) and finally we’ll see how to deal with Errors in React.js. I chose those frameworks, because they are by far the most popular at the moment, but you should be able to apply your new found knowledge to other frameworks easily!

A full sample project is available on github.

I. JavaScript Errors and generic handling

throw new Error('something went wrong') — will create an instance of an Error in JavaScript and stop the execution of your script, unless you do something with the Error. When you start your career as a JavaScript developer, you will most likely not do that yourself, but instead you have seen it from other libraries (or the runtime) doing it, e.g. `ReferenceError: fs is not defined` or similar.

The Error Object

The Error object has two properties built in for us to use. The first one is the message, which is what you pass as argument to the Error constructor, e.g. new Error('This is the message') . You can access the message through the message property:

const myError = new Error(‘please improve your code’)console.log(myError.message) // please improve your code

The second, very important one is the Error stack trace. You can access it through the `stack` property. The error stack will give you a history (call stack) of what files were ‘responsible’ of causing that Error. The stack also includes the message at the top and is then followed by the actual stack starting with the most recent / isolated point of the error and going down to the most outward ‘responsible’ file:

Error: please improve your code
at Object.<anonymous> (/Users/gisderdube/Documents/_projects/hacking.nosync/error-handling/src/general.js:1:79)
at Module._compile (internal/modules/cjs/loader.js:689:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10)
at Module.load (internal/modules/cjs/loader.js:599:32)
at tryModuleLoad (internal/modules/cjs/loader.js:538:12)
at Function.Module._load (internal/modules/cjs/loader.js:530:3)
at Function.Module.runMain (internal/modules/cjs/loader.js:742:12)
at startup (internal/bootstrap/node.js:266:19)
at bootstrapNodeJSCore (internal/bootstrap/node.js:596:3)

Throwing and Handling Errors

Now the Error instance alone does not cause anything. E.g. new Error('...') does not do anything. When the Error gets throw n, it gets a bit more interesting. Then, as said before, your script will stop executing, unless you somehow handle it in your process. Remember, it does not matter if you throw an Error manually, it is thrown by a library, or even the runtime itself (node or the browser). Let’s have a look at how we can deal with those Errors in different scenarios.

Photo by John Torcasio on Unsplash

try .... catch

This is the simplest, but often times forgotten way to handle errors — it does get used a lot more nowadays again, thanks to async / await, see below. This can be used to catch any kind of synchronous error. Example:

If we would not wrap the console.log(b) in a try … catch block, the script execution would stop.

… finally

Sometimes it is necessary to execute code in either case, whether there is an Error or not. You can use the third, optional block finally for that. Often, it is the same as just having a line after the try … catch statement, but sometimes it can be useful.

Enter asynchronity — Callbacks

Asynchronity, one topic you always have to consider when working with JavaScript. When you have an asynchronous function, and an Error occurs inside of that function, your script will have continued with the execution already, so there will not be any Error immediately. When handling asynchronous functions with callbacks (not recommended by the way), you usually receive two parameters in your callback function, which look something like this:

If there is an Error, the err parameter will be equal to that Error. If not, the parameter will be `undefined` or `null`. It is important to either return something in the if(err) -block, or to wrap your other instruction in an else -block, otherwise you might get another Error, e.g. result could be undefined and you try to access result.data or similar.

Asynchronity — Promises

A better way of dealing with asynchronity is using promises. Here, in addition to having more readable code, we also have improved error handling. We do no longer need to care so much about the exact Error catching, as long as we have a catch block. When chaining promises, a catch block catches all Errors since the execution of the promise or the last catch block. Note that promises without catch-block will not terminate the script, but will give you a less readable message like

(node:7741) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: something went wrong
(node:7741) DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code. */

Therefore, always add a catch block to your promises. Let’s have a look:

try … catch — again

With the introduction of async / await in JavaScript, we are back to the original way of handling Errors, with try … catch … finally, which makes handling them a breeze:

Since it also is the same way we used to handle ‘normal’, synchronous Errors, it is easier to have wider scoped catch statements — if desired.

II. Generating and handling Errors in the Server

Now that we have the tools to dealing with Errors, let’s see what we can actually do with them in real situations. Generating Errors and handling them correctly in the backend is a crucial part of your application. There are different approaches as to how to deal with Errors. I will show you an approach with a custom Error constructor and Error Codes, that we can easily pass to your frontend or any API-consumer. How you structure your backend in detail does not matter that much, the idea stays the same.

We will use Express.js as a routing framework. Let’s think about the structure we want to have the most efficient Error handling. We want:

  1. Generic Error handling, some kind of fallback, that basically just says: ‘Something went wrong, please try again or contact us’. This is not especially smart, but at least notifies the user that something is wrong — instead of infinite loading or similar.
  2. Specific Error handling in order to give the user detailed information about what is wrong and how to fix it, e.g. there is some information missing, the entry already exists in the database, etc.

Building a custom Error constructor

We will use the existing Error constructor and extend it. Inheritance in JavaScript is a risky thing to do, but in this case, I experienced it to be very useful. Why do we need it? We do still want the stack trace for us to have a nice debugging experience. Extending the native JavaScript Error constructor gives us the stack trace for free. The only thing we’re adding is a code , which we can later access through err.code, as well as a status (http status code) to pass to the frontend.

Photo by Jon Moore on Unsplash

How to handle routing

With our custom Error ready to use, we need to set up the routing structure. As I pointed out, we want a single point of truth for error handling, meaning for every route, we want to have the same error handling behaviour. By default, express does not really support that, since the routes are all encapsulated.

To resolve that issue, we can implement a route handler and define our actual route logic as normal functions. That way, in case the route function (or any function inside) throws an error, it will be returned to the route handler, which then can pass it to the frontend. Whenever an error occurs in the backend, we want to pass a response to the frontend — assuming a JSON API — in the following format:

{
error: 'SOME_ERROR_CODE',
description: 'Something bad happened. Please try again or contact support.'
}

Prepare yourself to be overwhelmed. My students were always angry at me when I said:

It is okay if you do not understand everything at first sight. Just use it and after a while you will find out why it makes sense.

As a side note, this is also called top-down learning, which I am a big fan of.

This is what the route handler itself looks like:

I hope you can read the comments in the code, I thought this makes more sense than explaining it here. Now let’s have a look at what an actual route file looks like:

In these examples, I am not doing anything with the actual request, I am just faking different error scenarios. So for example, GET /city would end up in line 3, POST /city would end up in line 8 and so on. This also works with query params, e.g. GET /city?startsWith=R . In essence, You will either have an unhandled Error, which the frontend will receive as

{
error: 'GENERIC',
description: 'Something went wrong. Please try again or contact support.'
}

or you will throw a `CustomError` manually, e.g.

throw new CustomError('MY_CODE', 400, 'Error description')

which turns into

{
error: 'MY_CODE',
description: 'Error description'
}

Now that we have this beautiful backend setup, we no longer have error logs leaking to the frontend and will always return usable information about what went wrong.

Make sure that you have a look at the full repo on github. Feel free to use it for any of your projects and modify it to fit your needs!

III. Displaying Errors to the User

The next and final step is to manage errors in the frontend. Here, you want to handle Errors produced by your frontend logic itself with the tools described in the first part. Errors from the backend however, also have to be displayed. Let’s first look at how we can display errors. As mentioned before, we will use React in our walkthrough.

Photo by Andersen Jensen on Unsplash

Saving Errors in React state

Just as other data, Errors and Error messages can change, therefore you want to put them in your components’ state. By default and on mounting, you want to reset the errors, so that when the user first sees the page, no Error is visible.

The next thing we have to clarify is different types of Errors with matching visual representation. Just as in the backend, there are 3 types:

  1. Global Errors, e.g. one of our generic Errors comes back from the backend or the user is not signed in, etc.
  2. Specific errors coming from the backend, e.g. the user sends his sign-in credentials to the backend. The backend answers that the password does not match. This cannot be verified by the frontend, so it has to come from the backend.
  3. Specific errors caused by the frontend itself, e.g. the validation of an e-mail input fails.

2. and 3. are very similar and can be handled in the same state (if wanted), but have a different origin. We will see in the code how that plays out.

I am going to use React’s native state implementation, but you could also use state management systems like MobX or Redux.

Global Errors

Usually, I save these Errors in the outermost stateful component and render a static UI Element, this could be a red banner at the top of your screen, a modal or anything else, the design implementation is up to you.

Sample UI Element for global Errors

Let’s have a look at the code:

As you can see, we have the Error in our state in the Application.js . We also have methods to reset and change the value of the error. We pass the value and the reset method down to the `GlobalError`-component, which will take care of displaying it and resetting it when clicking on the ‘x’. Let’s see how the GlobalError-component looks like:

As you can see in line 5, we do not render anything, if there is no Error. This prevents us from having an empty red box on our page at all times. Of course you can change the appearance and the behavior of this component. You could for example replace the ‘x’ with a Timeout that resets the error state after a couple of seconds.

Now, you are ready to use this global error state wherever you want, just pass down the _setError from Application.js and you can set the global Error, e.g. when a request from the backend comes back with the field error: 'GENERIC' . Example:

If you are lazy, you can stop here. Even if you have specific errors, you could always just change the global Error state and display the Error box at the top of the page. However, I am going to show you how to handle and display specific Errors. Why? First, this is the definite guide about handling errors, so I cannot just stop here. Second, UX people will probably freak out if you just display all errors globally.

Handling specific request Errors

Similar to the global errors, we can have local Error state inside other components as well. The procedure is the same:

One thing to remember is that clearing the Error usually has a different trigger. It would not make sense to have a ‘x’ to remove the Error. Here, it would make more sense to clear the Error when making a new request. You could also clear the Error when the user makes a change, e.g. when the input value is changed.

Frontend origin errors

As mentioned earlier, these Errors can be handled in the same way (state) as specific Errors coming from the backend. Let’s use the example with an input field this time and only allow the user to delete a city, when he actually provided input:

Error internationalisation using the Error code

Maybe you have been wondering why we have these Error codes, e.g. GENERIC , we were just displaying the error description passed from the backend. Now, as soon as your app grows, you will hopefully conquer new markets and at some point face the problem of having to support multiple languages. If you are at that point, you can use the mentioned Error code to display the correct caption in the user’s language.

I hope you gained some insight as to how to deal with Errors. Quickly typed and just as quickly forgotten console.error(err) should be a thing of the past now. It is essential to use that for debugging, but it should not end up in your production build. To prevent that, I would recommend you use a logging library, I have been using loglevel in the past and I am pretty happy with it.

About the Author: Lukas Gisder-Dubé co-founded and led a startup as CTO for 1 1/2 years, building the tech team and architecture. After leaving the startup, he taught coding as Lead Instructor at Ironhack and is now building a Startup Agency & Consultancy in Berlin. Check out dube.io to learn more.

--

--

Passionate about technology, design, startups and personal development. Bringing ideas to life at https://dube.io