Mistakes happen. That’s a given. According to Murphy’s law, whatever can go wrong, will go wrong. Your job, as a programmer, is to be prepared for that fact. You have a set of tools that are prepared to do precisely that. In this article, we go through them and explain how they work.
An error
When a runtime error occurs, a new error object is created and thrown. The browser presents you with the file name and the line number in which the problem occurs. Time for some evildoing:
1 |
cosnole.log('Hello world!'); |
I’ve put that code into my DevTools console and got that:
The thing that you might find striking here is the VM164. If you look it up in the chromium source code you discover that it has no special meaning and occurs if your code is not tied to a particular file. The same thing happens if you are using eval (which you probably shouldn’t do!). Otherwise, it is the name of the file in which the error occurs.
In this example, the ReferenceError is the message name and cosnole is not defined is the message.
Since we refer here to throwing the error, how about we catch it?
Try & catch
If an error is thrown inside of the try block, the execution of the rest of the code is stopped. The error is caught so that you can deal with it inside of the catch block. If no exceptions are thrown in the try block, the catch is skipped.
1 2 3 4 5 6 |
try { cosnole.log('Hello world!'); } catch(error) { console.log(error.name); // ReferenceError console.log(error.message); // cosnole is not defined } |
Even though back in the days all declared variables used to have a functional scope (since they were declared using the var keyword, not with const and let), the catch statement is an example of block scope. The catch block receives an identifier called error in the example above. It holds the value of what was thrown in the try block. Since it is a block-scoped, after the catch block finishes executing, the error is no longer available.
If you would like to know more about variable declarations in JavaScript, check out Scopes in JavaScript. Different types of variable declarations
An important thing to keep in mind is that if you don’t provide a variable name for the error in the catch statement, an error is thrown!
Try…catch works only for runtime errors. If the error comescame from the fact that your code is not valid JavaScript, it won’t be caught.
Asynchronous code
The try…catch mechanism can’t be used to intercept errors generated in asynchronous callbacks. This fact is a common source of a misunderstanding.
1 2 3 4 5 6 7 |
try { setTimeout(() => { cosnole.log('Hello world!'); }); } catch(error) { console.log(error); } |
Uncaught ReferenceError: cosnole is not defined at setTimeout
It does not work because the callback function passed to the setTimeout is called asynchronously. By the time it runs, the surrounding try…catch blocks have been already exited. You need to put them in the callback itself.
It is a lot better if you use promises. You can catch your errors using the catch function:
1 2 3 4 5 6 7 8 9 10 |
function failingFunction(){ return new Promise(() => { cosnole.log('Hello world!'); }) } failingFunction() .catch(error => { console.log(error); }); |
If you would like to know more about promises and callbacks check out Explaining promises and callbacks while implementing a sorting algorithm
The try…catch blocks make a comeback to deal with asynchronous code if you use async/await
1 2 3 4 5 6 7 8 9 10 11 12 13 |
function failingFunction(){ return new Promise(() => { cosnole.log('Hello world!'); }) } (async function() { try { await failingFunction(); } catch(error){ console.log(error); } })() |
If you are interested in async/await, read Explaining async/await. Creating dummy promises
Finally
There is one more clause that you can use, and it is finally. It will execute regardless of whether an exception is thrown.
1 2 3 4 5 6 7 8 9 |
let isLoading = true; try { const users = await fetchUsers(); console.log(users); } catch(error){ console.log('Fetching users failed', error); } finally { isLoading = false; } |
If you use the try block, you need to follow it with either the catch statement, the finally block or both. You might think that the finally statement does not serve any purpose, because you can write code just under the try…catch block. The fact is it is not always going to be executed. Examples of that are nested try-blocks.
1 2 3 4 5 6 7 8 9 10 11 12 |
try { try { cosnole.log('Hello world!'); // throws an error } finally { console.log('Finally'); } console.log('Will I run?'); } catch (error) { console.error(error.message); } |
In this example, the console.log('Will I run?') is not executed, because control is immediately transferred to the outer try’s catch-block. The finally is executed first even in such a case.
For more examples on how finally works, check out the code presented on MDN.
Throwing exceptions
JavaScript allows us to throw our own exceptions using the throw keyword. There is no restriction on the type of data that you throw.
1 2 3 4 5 |
try { throw 'Not good!'; } catch(error) { console.log(error); } |
The Error object
The error that gets thrown when the runtime error occurs inherits from Error.prototype and consists of two properties: the name of the error and the message. The name can be one of the predefined types of errors or a custom one.
The Error.prototype.constructor accept one argument, and it is the error message. If you would like to throw a non-generic error, you can use one of the predefined ones, or define a new one! To do this, you can use ES6 classes:
1 2 3 4 5 6 7 8 |
class MyError extends Error { constructor(message) { super(message); this.name = 'MyError'; } } throw new MyError('Not good!'); |
As you can see, the name of the error is now MyError. In Chrome it would be named after you class anyway, even without doing this.name = 'MyError', but it might not work that way in other browsers. When an error is thrown, most browsers display a whole stack so that you can see what exactly happen:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class MyError extends Error { constructor(message) { super(message); this.name = 'MyError'; } toString() { return 'The overriden error message'; } } function outerFunction() { innerFunction(); } function innerFunction() { throw new MyError('Not good!'); } outerFunction(); |
The Error.protototype.stack is non-standard, but it is implemented both by Chrome and Firefox.
1 2 3 4 5 |
try { outerFunction(); } catch(error) { console.log(error.stack); } |
Since it is not standardized, it behaves differently across browsers, so watch out!
Summary
In this article, we went through handling errors in JavaScript. It included describing how errors work in JavaScript and how to throw them. We’ve also covered the try…catch block and what purpose can the finally block serve. Aside from that, we’ve also explained the Error object in JavaScript and what are its properties, including some non-standardized ones. Hopefully, this will help you handle your errors better.
Good stuff for beginners! Thanks a lot!
thanks