In this article, you’ll learn to handle JavaScript errors and find out why seeing an error isn’t the worst case scenario.
If something can go wrong, it will.
— Murphy’s Law
Why errors exist
I’ll tell you a story. It has nothing to do with programming or JavaScript. But this story will help you understand the benefits of mistakes. And especially - why mistakes should not be hidden, but problems should be reported as early as possible.
Once upon a time, there was a girl named Mary. She was about to turn 5 years old. The parents wanted to surprise Mary and ordered a big pink cake. Mary dreamed about how she would blow out 5 candles and make her most cherished wish.
Mom ordered a cake from the best pastry shop in town. She made an order in advance. The pastry chef said that everything will be fine and the fairy will bring the cake in the midst of the holiday.
But, as time goes on, there is still no cake. Parents are trying to call to clarify the delivery time, but there is no answer.
Then there is a knock at the door. Mary runs to open it. She sees a wonderful fairy with pink wings. She has a huge box in her hands.
Everyone is in a wonderful mood, everyone is happy and looking forward to the culmination of the holiday. They open the box, and it’s empty.
Mary is crying, her parents are in shock, and the fairy does not understand anything. Nothing can be fixed. The end.
What’s happened
It was an ordinary day at the pastry shop. Order # 735443, which was placed by Mary’s mother, was not something special. Everything was done according to plan.
To begin with, consider the algorithm for the operation of the pastry shop:
- The pastry shop accepts the order
- The pastry shop transfers the order to the bakery
- The bakery fulfills the order
- The bakery passes the order back to the pastry shop
- The pastry shop packs the order
- The pastry shop transfers the order to the delivery service
- Delivery service delivers the order
An error can occur at any stage. Imagine a situation where everyone wants “the best” and instead of they prefer to remain silent about the problem.
It is possible that even at the first stage, the pastry shop did not receive enough information to fulfill the order. There could be communication problems and only the delivery date and address were in the order details.
Let’s see what happens next:
- The pastry shop accepts incomplete order
- The pastry shop sends incomplete order to the bakery
- The bakery is fulfilling an incomplete order (cool, nothing to do!)
- The bakery sends the incomplete order back to the pastry shop (and they don’t notice anything strange, since before there were no problems with incomplete order)
- The pastry shop packages incomplete order (all is well, the packaging is in stock)
- The pastry shop transfers the incomplete order to the delivery service
- Delivery service delivers incomplete order
Checks can be added to the pastry shop algorithm. For instance:
- At the stage of accepting an order, check that the order must include a product, delivery date and address.
- At the bakery stage, there must be a product in the order. The bakery is no longer interested in delivery date and address.
- At the stage of delivery, check that the address, delivery date and packed product are in the order. Problems this time with the address and date were not. But who knows what will happen tomorrow!
— It seems to me that it is enough to add all the checks at the first stage. Why repeat yourself?
— Great question. Each stage is a separate function. She is obliged to check the incoming parameters and if there is something with them out of order - report upstairs by throwing an error.
You don’t know how the algorithm will change tomorrow. You don’t know in what order the functions will be called.
The best way to avoid problems is to report the problem in step 1, of course. And if in an example from real life, this seems obvious, then in programming there is a very great temptation to “go further” instead of terminating the program execution if something goes wrong.
By adding checks at each step, we make sure we won’t always report an issue as quickly as we can.
Even if someone hires a stupid manager (rewrites the function of accepting an order), then a competent baker will not accept an order in which there is no information about what exactly he needs to cook.
How is JavaScript related to bakeries and birthday cakes
Similar problems can arise in programming. For example, suppose you write a function that takes a unique user ID and returns an array of his friends.
const getUserFriends = (userId) => {
if (!userId) {
return [];
}
const user = getUser(userId);
const userFriends = [];
for (let friendId of user.friends) {
userFriends.push(getUser(friendId));
}
return userFriends;
}
You don’t want the program to break, so you check the userId
at the very beginning of the function. If it is
not there, you return an empty array.
But is it good? Is it correct?
For the time being, you may not even notice the problem in the code. For example, everything works well, and you
always have userId
as input. This can last for years. But sooner or later other functions will start calling
your function, someone else will call them. As a result, when users start to see a white page instead of a list
of their friends, it will take a long and tedious task to figure out the reason.
Error Handling in JavaScript
An error in JavaScript is an object of type Error
.
const e = new Error();
You can add an error message to the error to provide more details about it.
const e = new Error('user_id is missing');
JavaScript errors have one interesting feature. They can be thrown.
To throw an error, you should use the throw
keyword.
if (Math.random() > 0.5) {
throw new Error('random error');
}
console.log('no error, execution continues');
The code above will throw an error in half the cases, and the program will stop executing.
In this case, you will see an error message on the screen and information about where the error occurred.
Error: random error
at /home/runner/9ag23k3avya/index.js:2:9
at Script.runInContext (vm.js:130:18)
at Object.<anonymous> (/run_dir/interp.js:209:20)
at Module._compile (internal/modules/cjs/loader.js:999:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1027:10)
at Module.load (internal/modules/cjs/loader.js:863:32)
at Function.Module._load (internal/modules/cjs/loader.js:708:14)
at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:60:12)
at internal/main/run_main_module.js:17:47
After the keyword throw
, you must specify an object of type Error
. Otherwise, there will be an error
(but hardly the one you expect).
throw 'an error!';
throw new Object;
throw false;
The example above will not work, because after the throw
I wrote a string, an object and a boolean value.
As soon as JavaScript notices the throw
keyword, it tries to terminate the execution of the program.
The only way to stop it and continue program execution is with a try/catch
block.
try {
if (Math.random() > 0.5) { // Math.random() returns a random number from 0 до 1. In half the cases it's greater than 0.5
throw new Error('random error'); // throw an error
}
console.log('no error'); // print this message if there's no error
} catch (e) {
console.log(e.message); // print the message about a 'random error' if it was thrown and got caught
}
console.log('execution continues'); // execution continues and this message is printed to the screen in any case
Before the block of code that can throw an error, I put the try
keyword and wrapped all the" dangerous code "
in curly braces.
Right behind it, I added a catch
block and handled the error inside it.
— I see that there are no parentheses after try
, but after catch
there is (e)
.
— Yes. It should be so.
In the catch block, we can analyze the error and decide what to do next. To work with the error, you need to somehow name it. Think of it as a parameter to a function.
You may well write something like catch (myError)
. But then inside the catch
block you will work with
object myError
, not e
.
How will the error message be displayed on the screen then?
— I think console.log (myError.message)
— Right. The error message that we indicated when creating it new Error ('random error')
,
can be obtained in the catch
block. It is in the message
field. Since you renamed the parenthesized error
after catch
, then you need to refer to the message
field not as e.message
, but as myError.message
.
The catch block is useful in that it gives you the ability to decide what to do next. In some cases, the program should be continued, while in others it is wiser to terminate.
You may well throw another error from the catch
block.
— I do not understand very well what the point is. Maybe you shouldn’t have caught her then?
— Maybe you shouldn’t. But the reasons for mistakes are different. The Error
object gives you the
opportunity to understand what’s going on, and decide on the further fate of the program.
In addition to try / catch
, there is another important block in JavaScript. It is called finally
.
The finally
block is executed regardless of whether an error occurs in the try
block.
One of the common scenarios for using a finally
block looks like this:
- a dangerous operation in the
try
block grabs resources (files, network, memory) - resources must be released in any case, regardless of whether the dangerous operation was successful
- resources are released in the
finally
block
try {
dangerousOperation();
} catch (e) {
handleError(e);
} finally {
performCleanUp();
}
You can skip the catch
block and use try/finally
without it.
try {
dangerousOperation();
} finally {
performCleanUp();
}
The code in the finally
block will still be executed even if an error occurs in the try
block.
— Weird. It seemed to me that the point of try
was just to catch the error in catch
.
And then what is the use of it?
— We are not always able to fix a bug in JavaScript.
It happens that we cannot continue the execution of the function in any way. In this case, we are obliged to report this error up to the function that called us.
With a catch
block, it will look like this.
try {
dangerousOperation();
} catch(e) {
throw e;
} finally {
performCleanUp();
}
However, if you remove the catch
block leaving only try/finally
, the behavior will remain the same.
The error will be pushed to the next level.