The try...catch
statements
As stated in the previous JavaScript Exceptions — Introduction chapter, ECMAScript 3 introduced the try
and catch
statements to JavaScript, which at that time were, and even today are, a near-standard way to handle errors in code.
Let's see how exactly do they work...
try
statement tests a given piece of code for any errors.It literally allows us to try executing a piece of code if it executes to completion.
If all goes well, great! However, if any error occurs amid the execution of the code, the corresponding catch
block comes into the game.
catch
statement contains an error-handling piece of code.It takes over when there is an error in the preceding try
block.
The try...catch
statements are instances of control-flow statements in JavaScript, some of which we've already explored in the JavaScript Conditions and JavaScript Loops units.
As with all control-flow statements, there is a precise syntax to using try
and catch
as shown below:
try {
// Code to test for errors.
}
catch (errorObject) {
// Code to handle any errors thrown in the try block above.
}
We start off with the try
keyword, followed a block statement that contains the code to test.
Next comes the catch
keyword (without any semicolon between the block of try
and catch
, similar to else
), followed by a pair of parentheses (()
) defining an identifier, followed by a block statement that contains the error-handling code.
When an error happens in a try
block, an error object is generated based on one of the error classes discussed above, and then passed on to the corresponding catch
block.
The catch
block must be able to receive this object in some identifier, and that's where the pair of parentheses, after the catch
keyword, comes into the equation. The pair of parentheses (()
) defines a variable that'll hold the error object dispatched by the try
statement, if any.
Simple?
Let's consider a quick example.
In the following code, we have the same set of statements as we had above accessing null
like a function. However, this time the statements are encapsulated inside a try
block:
try {
var value = null;
value();
}
catch (err) {
console.log('The error was handled.');
}
The consequence of this encapsulation is that when the statement value()
is executed (in line 3), a TypeError
is thrown as before, but instead of terminating the execution of the program, it's forwarded to the corresponding catch
statement, keeping the normal execution flow of the program intact.
The console output produced by this code is not an error message, but just the text 'The error was handled.' as shown below:
We could have just about any piece of code in the catch
block.
In the code below, we check the type of error thrown with the help of the instanceof
operator and then make a log based on the result:
try {
var value = null;
value();
}
catch (err) {
if (err instanceof TypeError) {
console.log('A value was used incorrectly.');
}
else {
console.log('At least not a type error.');
}
}
The console output clearly tells us that there was an error of the type TypeError
in the try
block.
Let's now work with the err
variable in the catch
block. Consider the following code, where we log the name
and the message
of the error object thrown:
try {
var value = null;
value();
}
catch (err) {
console.log('Name:', err.name);
console.log('Message:', err.message);
}
Here's the console output produced:
The message is automatically formed by the browser the moment the error is encountered, and it's the exact same one that's displayed when the error is logged in the console unhandled (in red color).
The finally
statement
A third statement was added along with the try...catch
statements in ECMAScript 3 to execute a piece of code regardless of any outcome in the try
and catch
blocks.
That statement is finally
.
finally
statement is used to execute a piece of code at the end of a try...catch
statement.Syntactically, finally
must come after a try
statement but before the corresponding catch
statement, if there is any.
Akin to try
, finally
doesn't have any pair of parentheses (()
) as does the catch
statement.
Here's its syntax:
try {
// Code to test for errors.
}
catch (errorObject) {
// Code to handle any errors thrown in the try block above.
}
finally {
// Code to execute in the end.
}
Let's consider an example of the usage of finally
.
In the snippet below, we have the same code as before, with the addition of a finally
block:
try {
var value = null;
value();
}
catch (err) {
console.log('The error was handled.');
}
finally {
console.log('Finally')
}
Let's see the console output produced:
As the try
block run into an error, execution moves into the catch
block, as a result, logging the text 'The error was handled.' Then once the catch
block completes, execution moves into the finally
block logging 'Finally'.
After the finally
block, execution moves out of the entire try
family of statements to the very next statement. Since in the case above, there was nothing beyond finally
, its completion marked the completion of the code as well.
Now a fair question at this point would be that: if execution jumps out of try...catch
onto the next statement anyway, what's the real point of finally
?
For example, consider the following code:
try {
var value = null;
value();
}
catch (err) {
console.log('First');
}
console.log('Second');
If we were to incorporate this same logic but with finally
in place, the console output would nonetheless still be the same:
try {
var value = null;
value();
}
catch (err) {
console.log('First');
}
finally {
console.log('Second');
}
This then leads to the same question as before: what's the purpose of finally
?
Well, technically, there is absolutely no benefit of finally
in the code shown above. But there might be some case where we might find finally
particularly helpful.
The thing is that finally
is quite a powerful construct. It's guaranteed to execute at the end of a set of try
and catch
statements. This guarantee isn't there for code after the finally
block.
As a matter of fact, finally
gets executed even if a value is returned from the preceding try
or catch
blocks; it's executed even if the catch
block runs into an error itself.
Let's consider an example of this.
Take a look at the code below without the finally
block:
try {
var value = null;
value();
}
catch (err) {
someRandomVariable;
}
console.log('Finally');
The catch
block contains an error itself. Our job is to see the output produced by this code:
Not surprisingly, the code throws an error and, most imporantly, the console.log()
statement doesn't get executed either.
This example also illustrates the fact that it's only the try
statement that's meant to test for errors; the catch
block isn't concerned with testing a piece of code for errors. If there is any error in a catch
block, it would get thrown.
Now, take a look over the code below, this time with finally
:
try {
var value = null;
value();
}
catch (err) {
someRandomVariable;
}
finally {
console.log('Finally');
}
The code still throws an error, but notice the first log. It confirms that the finally
block got executed.
If you're very curious, you might want to know exactly how does the engine execute the code above. At first glance, it might seem that finally
is executed before catch
, and hence the log, but this isn't the case.
Is finally
executed before catch
?
To start off by answering this question, no, finally
doesn't execute before catch
.
As per the order of the statements, as defined by the grammar of JavaScript, finally
always executes after the preceding catch
block.
But this doesn't quite resonate well with the output in the code above. The string 'Finally'
was logged before the error message, which hints us that the finally
block was executed before catch
.
And here comes the interesting part.
Internally, when the engine is executing the catch
block, it checks for any errors. If an error is encountered, as always, it immediately halts execution in the catch
block, but instead of turning to make an alert in the console for the error, it turns to the finally
block, if there is any.
At the same time, the engine remembers that there is a pending error to deal with once the finally
block is done executing.
Now two things could happen in the finally
block:
- It contains an error. In this case, this very error (in
finally
) is thrown and logged in the console. The error in thecatch
block is ignored. - It doesn't contain any error. In this case, the error from the
catch
block is thrown.
We can confirm all of this discussion with the help of a very simple piece of code, in fact, just with a couple of additions to our previous code.
In the following code, we have sprinkled a bunch of console.log()
statements here and there to learn more about the order of execution of the try
, catch
and finally
statements:
try {
console.log('Try 1');
var value = null;
value();
console.log('Try 2');
}
catch (err) {
console.log('Catch 1');
someRandomVariable;
console.log('Catch 2');
}
finally {
console.log('Finally');
}
console.log('Outside');
Let's now see the output produced here. But before we do that, try to reason yourself about the output. It really isn't that difficult.
Anyways, time to see the console:
Reasoning:
Execution begins with the try
block. The string 'Try 1'
is logged to the console. Thereafter, an error is encountered in line 4 and thus execution is halted beyond that point in the try
block and instead shifted to the catch
block.
In the catch
block, the string 'Catch 1'
is logged to the console. This confirms that catch
does indeed execute after try
. Thereafter, an error is encountered in line 9 and thus execution is halted beyond that point in the catch
block and instead shifted to the finally
block. The engine remembers that there is a pending error from the catch
block to deal with.
In the finally
block, the string 'Finally'
is logged. This completes the finally
block and likewise the engine resumes with the pending error from the catch
block. This error is logged in the console.
And thereby, execution is halted beyond the finally
block by virtue of the error thrown in the catch
block.
Easy?
The throw
keyword
As we saw above, there are many instances where the JavaScript engine automatically throws exceptions without having us to get involved in the error-generating process. It forms its own message and decides which error class to use, e.g. SyntaxError
, TypeError
, etc, all by itself.
Now most of the times, we are good with this setting. But sometimes, we might want to control the error dispatch. Said in other words, sometimes we might want to throw a custom error in a given scenario.
As a very simple example, suppose we create a function sum()
to be used as a utility function in another JavaScript program:
function sum(a, b) {
return a + b;
}
Since this will be used in another program (most probably not written by us), we can't guarantee that it'll be used in the correct way, which is to provide it with two arguments, both numbers.
In the console snippet below, we call this function with true
and 10
as the arguments, and get 11
as the return value where we should've ideally got an error thrown because of the non-numeric value true
:
sum(true, 10)
A better option is to check whether each of the arguments to sum()
is a number and then throw an error if one is not. And in this regard, we can use the throw
keyword as shown below:
function sum(a, b) {
if (typeof a !== 'number' || typeof b !== 'number') {
throw new TypeError('Arguments to sum() must only be numbers.');
}
return a + b;
}
Let's understand what exactly throw
is.
The throw
keyword was amongst the error-handling utilities introduced into JavaScript with ECMAScript 3.
throw
is used to manually throw an error.The syntax of throw
is as follows:
throw errorDescription;
The throw
keyword is followed by a value to describe the error thrown. Technically, errorDescription
can be any valid value in JavaScript, but there is no point in using just about any arbitrary value.
The only sensible types of values suitable as errorDescription
are numbers (for error codes), strings (for plain error messages) and, best of all, instances of the error classes that we discussed above, i.e. SyntaxError
, TypeError
, ReferenceError
, etc.
If there is try
block surrounding the throw
statement, errorDescription
is provided to the corresponding catch
block.
Coming back to our example, recall that being provided with the wrong type of arguments is a TypeError
concern. Likewise, we set up an if
conditional to check the types of both a
and b
and throw an error if at least one of them is not a number.
Let's review the code:
function sum(a, b) {
if (typeof a !== 'number' || typeof b !== 'number') {
throw new TypeError('Arguments to sum() must only be numbers.');
}
return a + b;
}
And now let's try making the same erroneous call as before, i.e. sum(true, 10)
:
sum(true, 10)
Perfect! The code doesn't run and thus we succeed in preventing it from running in a scenario where the wrong type of arguments are provided.
Things to note
In this section, we learn about a couple of things to keep in mind while working with try
, catch
, finally
and throw
.
try
doesn't entertain syntax errors
As we saw above, the try
statement can be used to check if a given piece of code has any errors in it, and thereby handle those errors.
For instance, in the familiar code below, the statements inside the try
block raise a TypeError
exception and thus get the catch
block to intervene:
try {
var value = null;
value();
}
catch (err) {
console.log('The error was handled.');
}
However, it's paramount to know that this isn't the case with syntax errors in code.
The try
block can entertain all kinds of errors but not those in the grammar (syntax) of a code.
try {
var = 10;
}
catch (err) {
console.log('The error was handled.');
}
This behavior of JavaScript shouldn't be considered a bad design decision. In fact, it's a really good one. A syntax error can be detected right when parsing code, and if one is found, the code shouldn't ideally be executed until that syntactic invalidity is resolved.
Thus, said in another way, the try
block can only catch those errors that happen during the execution of a given program. Syntax errors almost always happen while parsing the program, and hence aren't handled by try
.
catch
and finally
are optional
The catch
and finally
blocks aren't mandatory to be included for every try
block.
This might be sensible for finally
since we've already seen a couple of code examples without it, but we haven't seen any example without catch
.
Consider the following code:
console.log('Before');
try {
someRandomVariable;
}
console.log('After');
There is no catch
block associated with the try
statement, yet the error (precisely, the ReferenceError
) inside try
doesn't get delegated to the console as an alert message. The code executes normally making the following logs:
Omitting catch
for a try
statement is done only when we aren't interested in handling any errors in the try
block.
The aim of try
here is just to make sure that due to any possible errors in the code inside it, the entire script's execution doesn't come to a halt. If any error is encountered in try
, it would be silently ignored due to no corresponding catch
block, and execution would continue in its normal flow.
finally
can come immediately after try
Thus far, we've seen the catch
statement coming after try
, and then the finally
statement coming after catch
.
However, this doesn't necessarily tell us that finally
can also come immediately after try
, given that there is no catch
statement.
For instance, consider the following code:
try {
someRandomVariable;
}
finally {
console.log('Finally');
}
We have a try
block, with an error, and a finally
block to execute a piece of code after try
. As before, there is no catch
block, and so the error raised in try
gets ignored silently, moving execution into finally
.
Keep in mind that if there is a catch
statement associated with a try
statement, then it would be invalid to write the finally
statement before it.
An illustration follows:
try {
someRandomVariable;
}
finally {
console.log('Finally');
}
catch (err) {
console.log('The error was handled.')
}
While reading this code, the parser doesn't find any syntax error up until the end of the finally
block — it's perfectly alright to include finally
after try
.
However, going beyond finally
, it encounters catch
where it doesn't expect it, and thus raises a SyntaxError
clearly explaining the fact that an expected keyword catch
was encountered.