WebAssembly in Action

Author of the book "WebAssembly in Action"
Save 40% with the code: ggallantbl
The book's original source code can be downloaded from the Manning website and GitHub. The GitHub repository includes an updated-code branch that has been adjusted to work with the latest version of Emscripten (currently version 3.1.44).

Friday, August 13, 2010

JavaScript: Try-Catch-Finally and Throw

I ran into an issue a while ago where, on occasion, the window that opened a pop-up was refreshed while the pop-up window was still open.

When the pop-up window tried to call a function within the opener window a 'Permission denied' exception was thrown because the reference to the opener window was no longer valid.

After a bit of searching for a solution I ran across a feature of JavaScript that I didn’t realize was available. The solution was a try-catch statement.

At the time, I didn't realize that JavaScript supported anything more than a simple try-catch block so I didn't dig any further. It was only recently that I ran across an article that opened my eyes to what was possible in JavaScript when it comes to error handling.



The Try-Catch Statement

If an error can be avoided simply by checking to see if an object is null, for example, before using the object then best practices are to test for the null condition rather than use a try-catch statement.

A try-catch statement is used if you have code that might throw an exception and there is no way to test for the error condition. The statement gives you the opportunity to fail gracefully when unexpected errors happen.


The way a try-catch statement works is that you place the code that might throw an unexpected error within a try block. If an error does occur, execution is transferred from the try block to the matching catch block. The catch block can then display or log the error message.

The following is a simple example of a try-catch statement:
try
{

// Do some work that might produce an
// unexpected error
DoSomeWork();

}
catch (e)
{

// Depending on the error you may want
// to log it or display it to the user.
// In this case, display it to the user.
alert("The following error has occurred: " + e.message);

}

The catch block is optional but I recommend that it should only be omitted if you are nesting try-catch statements and only if there is a catch statement further up in the hierarchy.

If no catch statement exists in the hierarchy of nested try-catch statements, once the optional finally blocks finish executing, the execution is transferred to the page so that the error can be logged and/or displayed to the user by the browser. The JavaScript that would normally happen following the code that triggered the exception, will not happen as a result since the exception was not handled by your code.



The Finally Block

There is an optional 'finally' block that can be included with the try-catch blocks and is always the last block of the try-catch statement.

If included, the finally block's code gets executed regardless of if there was an error nor not.

The following is a simple example of a try-catch statement that includes the finally block:
try
{

// Do some work that might produce an
// unexpected error
DoSomeWork();

}
catch (e)
{

// Depending on the error you may want
// to log it or display it to the user.
// In this case, display it to the user.
alert("Error: " + e.message);

}
finally
{

// release resources that might have
// been allocated before the try block
// began


}


Nesting Try-Catch Statements

You can nest try-catch statements.

If an exception happens within an inner try-catch statement and the try block has no matching catch statement then, after the optional finally block finishes execution, the catch block of the wrapping try-catch statement will be transferred the execution of the code so that it can handle the exception.

Letting the exception bubble up the hierarchy of try-catch statements is up to you and can have its uses. One scenario could be that you just have a try-finally block with no catch so that resources are released and then allow the parent try-catch statement to handle the error logging.



The Error object

The standard error object that is usually passed to the catch statement has the following properties (some browsers have additional properties):
  • name - the class of the Error ('DOMException' or 'Error' for example)
  • message - the error message

Depending on who's throwing the error (the browser, a third party's JavaScript library, or even your own code), the error may or may not be the standard Error object and as a result it may or may not contain the properties mentioned above.


It is good practice to test if the desired property exists before trying to access it.



Handling Multiple Types of Exceptions

As you will see in the upcoming Throw section, a variety of items can be thrown.

Sometimes it's nice to be able to do specific error handling for specific types of errors.

With a single catch block this can be achieved by implementing an if/else-if statement, within the catch block, to test which type of error was received and then process the error accordingly as in the following example:

for (var iCounter = 0; iCounter < 3; iCounter++)
{
// Try/catch here in the loop so that if
// DoWork throws an exception, the loop
// will continue on
try
{

DoWork(iCounter);

}
catch (e)
{

// Test for our own specific error
// conditions
if (e == "ItemNotFoundException") { HandleItemNotFoundException(e); }

else if (e == "InvalidCredentialsException") { HandleInvalidCredentialsException(e); }

else if (e == "InvalidDateException") { HandleInvalidDateException(e); }

// We didn't hit one of our own exception
// conditions above so this one isn't
// ours. Handle the standard error...
else { LogSystemError(e); }

}
}

// Simply a function to throw a different
// exception string for each iCounter value
// passed in (simulate some errors)
function DoWork(iCounter)
{
if (iCounter == 0) { throw "ItemNotFoundException"; }

else if (iCounter == 1) { throw "InvalidCredentialsException"; }

else if (iCounter == 2) { throw "InvalidDateException"; }
}


Note: Firefox is the only major browser that supports multiple catch blocks and, as a result, I recommend against using them simply because multiple catch blocks are not cross-browser compatible.



The Throw Statement

Based on what you've read so far, you now know that there are at least two types of items that can be thrown: an Error object and a string.

So far we've caught Error objects but have only thrown strings. The following is an example of how you throw an Error object from your own code:
throw new Error("custom error message");

You can actually throw any expression including strings, numbers, objects, true, false, and null. Some expressions would be more valuable to an error handler within a catch statement than others.


You need to be careful if not passing an Error object as an exception because throwing a string, number, true, false, or null will pass the value to the catch statement 'as is'. The result is that you could end up with the Error object in some cases and your own values (strings, numbers, etc) in other cases.

If you are throwing error expressions that don't match the Error object's structure, you would want to do some form of type checking (typeof, instanceof) on the error object in your catch statements to handle the error correctly in all cases.



Rethrowing the Exception

There are times where you might want to respond to certain error conditions that are specific to your block of code but if a system error occurs, for example, maybe you just want to just let the exception continue up the chain of try-catch statements.

The following is an example that rethrows an exception:
try
{

DoWork(iCounter);


}

catch (e)
{

// Test for our own specific error
// conditions
if (e == "ItemNotFoundException") { HandleItemNotFoundException(e); }

else if (e == "InvalidCredentialsException") { HandleInvalidCredentialsException(e); }

else if (e == "InvalidDateException") { HandleInvalidDateException(e); }

// We didn't hit one of our own
// exception conditions above so this
// one isn't ours. Let one of the
// parent try-catch blocks handle the
// error
else { throw e; }

}


Custom Exceptions

Being able to throw a custom object can be of use in that you can differentiate your own types of errors (CustomException for example) from system errors (Error object).

Throwing a string would be more useful as a way to output a specific error message rather than doing custom error handling when a certain type of error occurs.

The following is an example of the creation and throwing of a custom exception object:

// Our CustomException class declaration
function CustomException(sErrorMessage)
{
this.name = "CustomException";
this.message = sErrorMessage;

this.toString = function () { return this.message; }
}


try
{
...

// We hit an issue. Throw our custom
// exception
throw new CustomException("Processing error...item has already been specified");
}
catch (e)
{
// If the exception is our custom
// exception...
if (e instanceof CustomException)
{

alert("Error: " + e.message);


}

else // Standard Error object
{

// Just rethrow and let the parent
// try/catch statements deal with it
throw e;

}
}



In Closing

When I started looking at this topic as a potential article, I didn't think there would be much to write about. As I started digging deeper, however, I realized that there is much more to the try-catch statement than I originally thought.

I hope this article was of use to you.

No comments:

Post a Comment