When writing our programs, we want to be informed of every error. However when we've finally deployed the code to a web server for the whole world to access, the last thing we want the user to be seeing is a lot of error message dialog boxes. Of course, writing bug-free code would be a good start, but keep the following points in mind:
Occasions arise when conditions beyond our control lead to errors. A good example of this is when we are relying on something, such as a Java applet, which isn't on the user's computer and where there is no way of checking for its existence.
Murphy's Law states that anything that can go wrong will go wrong!
The best way to handle errors is to stop them from occurring in the first place. That seems like stating the obvious, but there are a number of things we should do if we want error-free pages.
Check pages thoroughly on as many different platforms and browsers as possible. I realize this is easier said than done given the number of possible variations and that it's easier when it's a professional website created by a team of people with the hardware, software, and time to check platform and browser compatibility. The alternative is for us to decide which browsers and platforms are supported and state these requirements on our first web page. Then check that it works with the specified combinations. Use the browser and platform checking code we saw earlier in the book to send unsupported users to a nice safe and probably boring web page with reduced functionality, or maybe just supply them with a message that their browser/platform is not supported.
Validate our data. If there is a way users can enter dud data that will cause our program to fall over, then they will. As a poster on the wall of somewhere I worked once said, "Users are idiots. Never underestimate the cunning of an idiot." If our code will fall over if a text box is empty, we must check that it has something in it. If we need a whole number, we must check that that's what the user has entered. Is the date the user just entered valid? Is the e-mail address "mind your own business" the user just entered likely to be valid? No, so we must check that it is in the format something@something.something.
Okay, so let's say we've carefully checked our pages and there is not a syntax or logic error in sight. We've added data validation that confirms that everything the user enters is in a valid format. Things can still go wrong, and problems arise that we can do nothing about. Let's use a real-world example of a situation that happened to me.
I created an online message board that used something called remote scripting, a Microsoft technology that allows us to transfer data from a server to a web page. This relies on a small Java applet to enable the transfer of data. I checked my code and everything was fine. I launched it live, and again it worked just fine, except in about 5 percent of cases where the Java applet initialized, but then caused an error. To cut a long story short, remote scripting worked fine, except in a small number of cases where the user was behind a particular type of firewall (a firewall is a method of stopping hackers from getting into a local computer network). Now, there is no way of determining whether a user is behind a certain type of firewall, so there is nothing that can be done in that sort of exceptional circumstance, or is there?
In fact IE 5+ and Netscape 6+ include something called the try...catch statement. It's also part of the ECMAScript 3 standard. This allows us to try to run our code; if it fails, the error is caught by the catch clause and can be dealt with as we wish. In my message board example, I used a try...catch clause to catch the Java applet's failure and redirected the user to a more basic page that still displayed messages, but without using the applet, and it therefore was not affected by the firewall.
Let's now look at the try...catch statements.
The try...catch statements work as a pair; we can't have one without the other.
We use the try statement to define a block of code that we want to try to execute.
We use the catch statement to define a block of code that will execute if an exception to the normal running of the code occurs in the block of code defined by the try statement. The term exception is key here: It means a circumstance that is extraordinary and unpredictable. Compare that with an error, which is something in the code that has been written incorrectly. If no exception occurs, the code inside the catch statement is never executed. The catch statement also enables us to get the contents of the exception message that would have been shown to the user had we not caught it first.
Let's create a simple example of a try...catch clause.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <body> <script language=JavaScript type="text/javascript"> try { alert('This is code inside the try clause'); alert('No Errors so catch code will not execute'); } catch(exception) { if (exception.description == null) { alert("Error is " + exception.message) } else { alert("Error is " + exception.description); } } </script> </body> </html>
Save this as TryCatch.htm.
First we define the try statement; we mark out which block of code is within the try statement by enclosing it in curly braces.
Next comes the catch statement. We've included exception in parentheses right after the catch statement. This exception is simply a variable name. It will store an Exception object containing information about any exception thrown during execution of code inside the try code block. Although we've used exception, we could use any valid variable name. For example, catch(exceptionObject) would be fine and certainly more descriptive.
The Exception object contains two properties that provide information about the exception that occurred. The bad news is that while both IE 5 and NN 6 support the Exception object and both have two properties, the names of these properties differ.
IE's version of the Exception object has the number and description properties. The number property is a unique number for that error type. The description property is the error message the user would normally see.
With NN 6+, the properties of the Exception object are name and message. The name property is a unique name for that type of error and the message property is much like IE's description property in that it gives a more detailed explanation of what went wrong. These properties are also part of the ECMAScript 3 standard and IE 5.5+ and Opera 6+ support them.
Within the curly braces after the catch statement is the code block that will execute if and only if an error occurs. In this case, the code within the try code block is fine, and so the alert() method inside the catch block won't execute.
Let's insert a deliberate error.
try
{
alert('This is code inside the try clause');
ablert ('Exception will be thrown by this code');
}
catch(exception)
{
if (exception.description == null)
{
alert("Error is " + exception.message)
}
else
{
alert("Error is " + exception.description);
}
}
Now when we load the page the first alert() method, the try block of code, will execute fine and the alert box will be displayed to the user. However, the second ablert() statement will cause an error and code execution will start at the first statement in the catch block.
Because the IE 5 and NN 6 Exception objects support different properties, we need different code for each. How do we tell whether the browser is IE or NN?
By checking to see whether the exception.description property is null, we can tell whether the description property is supported, and therefore whether the browser is one of those supporting the property such as IE. If it is equal to null, the property is not supported by the browser, so we need to display the message property of the Exception object instead. If it is not null, the description property has a value and therefore does exist and can be used.
If you're using Internet Explorer 5.0+, the error description displayed will be "Object expected." If you're using Netscape Navigator 6, the same error is interpreted differently and reported as "ablert is not defined."
If we change the code again, so it has a different error, we'll see something important.
try { alert('This is code inside the try clause'); alert('This code won't work'); } catch(exception) { if (exception.description == null) { alert("Error is " + exception.message) } else { alert("Error is " + exception.description); } }
If we were to load this code into an IE 5 or NN 6 browser, instead of the error being handled by our catch clause, we get the normal browser error message telling us "Expected ')'."
The reason for this is that this is a syntax error; the functions and methods are valid, but we have an invalid character. The single quote in the word won't has ended the string parameter being passed to the alert() method. At that point JavaScript syntax, or language rules, specifies that a closing parenthesis should appear, which is not the case here. Before executing any code, JavaScript goes through all the code and checks for syntax errors, or code that breaches JavaScript's rules. If a syntax error is found, the browser deals with it as normal; our try clause never runs and therefore cannot handle syntax errors.
The throw statement can be used within a try block of code to create our own run-time errors. Why create a statement to generate errors, when a bit of bad coding will do the same?
Throwing errors can be very useful for indicating problems such as invalid user input. Rather than lots of if...else statements, we can check the validity of user input, then use throw to stop code execution in its tracks and cause the error catching code in the catch block of code to take over. In the catch clause, we can determine whether the error is user input–based, in which case we can notify the user what went wrong and how to correct it. Alternatively, if it's an unexpected error, we can handle it more gracefully than lots of JavaScript errors.
To use throw, we type throw and include the error message after it.
throw "This is my error message";
Remember that when we catch the Exception object in the catch statement, we can get hold of the error message that we have thrown. Although I've used a string in my throw statement, we can actually throw any type of data, such as numbers and objects.
In this example we'll be creating a simple factorial calculator. The important parts of this example are the try...catch clause and the throw statements. It's a frameset page to enable us to demonstrate that things can go wrong that we can't do anything about. In this case, the page relies on a function defined within a frameset page, so if the page is loaded on its own, a problem will occur.
First let's create the page that will define the frameset and also contains an important function.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <title>Example</title> <script language=JavaScript type="text/javascript"> function calcFactorial(factorialNumber) { var factorialResult = 1; for (; factorialNumber > 0; factorialNumber--) { factorialResult = factorialResult * factorialNumber; } return factorialResult; } </script> </head> <frameset COLS="100%,*"> <frame name="fraCalcFactorial" src="CalcFactorial.htm"> </frameset> </html>
Save this page as CalcFactorialTopFrame.htm.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <title>Example</title> <script language=JavaScript type="text/javascript"> function butCalculate_onclick() { try { if (window.top.calcFactorial == null) throw "This page is not loaded within the correct frameset"; if (document.form1.txtNum1.value == "") throw "!Please enter a value before you calculate its factorial"; if (isNaN(document.form1.txtNum1.value)) throw "!Please enter a valid number"; if (document.form1.txtNum1.value < 0) throw "!Please enter a positive number"; document.form1.txtResult.value = window.parent.calcFactorial(document.form1.txtNum1.value); } catch(exception) { if (typeof(exception) == "string") { if (exception.charAt(0) == "!") { alert(exception.substr(1)); document.form1.txtNum1.focus(); document.form1.txtNum1.select(); } else { alert(exception); } } else { if (exception.description == null) { alert("The following error occurred " + exception.message) } else { alert("The following error occurred " + exception.description); } } } } </script> </head> <body> <form name="form1"> <input type="text" name=txtNum1 size=3> factorial is <input type="text" name=txtResult size=25><br> <input type="button" value="Calculate Factorial" name=butCalculate onclick="butCalculate_onclick()"> </form> </body> </html>
Save the page as CalcFactorial.htm. Then load the first page, CalcFactorialTopFrame.htm, into your browser. Remember that this works only with IE 5 and later and NN 6 and later and Opera 6/7.
The page consists of a simple form with two text boxes and a button. Enter the number 4 into the first box, and then click the Calculate Factorial button. The factorial of 4, which is 24, will be calculated and put in the second text box. (See Figure 10-37.)
The factorial of a number is the product of all the positive integers less than or equal to that number. For example, the factorial of 4 (written 4!) is 1 * 2 * 3 * 4 = 24. Factorials are used in various branches of mathematics, including statistics. Here, we just want to create a function that does something complex enough to be worthy of a function, but not so complex as to distract us from the main purpose of this example: the try...catch and throw statements.
If we clear the first text box and click Calculate Factorial, we'll be told that a value needs to be entered. If we enter an invalid non-numeric value into the first text box, we'll be told to enter a valid value. If we enter a negative value, we'll be told to enter a positive value.
Also, if we try loading the page CalcFactorial.htm into our browser and enter a value in the text box and click Calculate Factorial, we'll be told that the page is not loaded into the correct frameset.
As we'll see, all of these error messages are created using the try...catch and throw statements.
Because this example is all about try...catch and throw, we'll concentrate just on the CalcFactorial.htm page, in particular the butCalculate_onclick() function, which is connected to the onclick event handler of the form's only button.
We'll start by looking at the try clause and code inside it. The code consists of four if statements and another line of code that puts the calculated factorial into the second text box. Each of the if statements is checking for a condition which, if true, would cause problems for our code.
The first if statement checks that the calcFactorial() function, in the top frameset window, actually exists. If not, it throws an error, which will be caught by the catch block. If the user had loaded the CalcFactorial.htm page rather than the frameset page CalcFactorialTopFrame.htm, then without this throw statement our code would fail.
try { if (window.top.calcFactorial == null) throw "This page is not loaded within the correct frameset";
The next three if statements check the validity of the data entered into the text box by the user. First we check that something has actually been entered in the box, then that what has been entered is a number, and then finally we check that the value is not negative. Again if any of the if conditions is true, we throw an error, which will be caught by the catch block. Each of the error messages we define starts with an exclamation mark, the purpose of which is to mark the error as a user input error, rather than an error such as not being in a frameset.
if (document.form1.txtNum1.value == "") throw "!Please enter a value before you calculate its factorial"; if (isNaN(document.form1.txtNum1.value)) throw "!Please enter a valid number"; if (document.form1.txtNum1.value < 0) throw "!Please enter a positive number";
If everything is fine, the calcFactorial() function will be executed and the results text box will be filled with the factorial of the number entered by the user.
document.form1.txtResult.value = window.parent.calcFactorial(document.form1.txtNum1.value); }
Finally let's turn our attention to the catch part of the try...catch statement. First, any message thrown by the try code will be caught by the exception variable.
catch(exception) {
The type of data contained in exception will depend on how the error was thrown. If it was thrown by the browser and not by our code, exception will be an object, the Exception object. If it's thrown by our code, then in this instance we've thrown only primitive strings. So the first thing we need to do is decide what type of data exception contains. If it's a string we know it was thrown by our code and can deal with it accordingly. If it's an object, and given that we know none of our code throws objects, we assume it must be the browser that has generated this exception and that exception is an Exception object.
if (typeof(exception) == "string") {
If it was code that generated the exception using a throw (and so exception is a string), we now need to determine whether the error is a user input error, such as the text box not containing a value to calculate, or whether it was another type of error, such as the page not being loaded in our frameset. All the user input exception messages had an exclamation mark at the beginning, so we use an if statement to check the first character. If it is a !, we notify the user of the error and then return focus to our control. If it's not, we just display an error message.
if (exception.charAt(0) == "!") { alert(exception.substr(1)); document.form1.txtNum1.focus(); document.form1.txtNum1.select(); } else { alert(exception); } }
If exception was not a string, we know we have an exception object and need to display either the message property if it's NN 6+ or the description property if it's IE 5+. We use if e.description == null check to see which property is supported.
else { if (exception.description == null) { alert("The following error occurred " + exception.message) } else { alert("The following error occurred " + exception.description); } } }
So far we've been using just one try...catch statement, but it's possible to include a try...catch statement inside another try statement. Indeed, we could go further and have a try...catch inside the try statement of this inner try...catch, or even another inside that, the limit being what it's actually sensible to do.
So why would we use nested try...catch statements? Well, we can deal with certain errors inside the inner try...catch statement. If, however, it's a more serious error, the inner catch clause could pass the error to the outer catch clause by throwing it to it.
Let's look at an example.
try { try { ablurt ("This code has an error"); } catch(exception) { var eMessage if (exception.description == null) { eMessage = exception.name; } else { eMessage = exception.description; } if (eMessage == "Object expected" || eMessage == "ReferenceError") { alert("Inner try...catch can deal with this error"); } else { throw exception; } } } catch(exception) { alert("Error the inner try...catch could not handle occurred"); }
In this code we have two try...catch pairs, one nested inside the other.
The inner try statement contains a line of code that contains an error. The catch statement of the inner try...catch checks the value of the error message caused by this error. If the exception message is either "Object expected" or ReferenceError, the inner try...catch deals with it by way of an alert box.
In fact, both the exception messages that are checked for are the same thing, but reported differently by IE and NN. Note that I've used NN's Exception object's name property for comparison rather than the message because name is a much shorter one-word description of the exception in comparison to message, which is a sentence describing the exception.
However, if the error caught by the inner catch statement is any other type of error, it is thrown up in the air again for the catch statement of the outer try...catch to deal with.
Let's change the calcFactorial() function from the previous example, CalcFactorial.htm, so that it has an inner and outer try...catch.
function butCalculate_onclick() { try { try { if (window.top.calcFactorial == null) throw ("This page is not loaded within the correct frameset"); if (document.form1.txtNum1.value == "") throw("!Please enter a value before you calculate its factorial"); if (isNaN(document.form1.txtNum1.value)) throw("!Please enter a valid number"); if (document.form1.txtNum1.value < 0) throw("!Please enter a positive number"); document.form1.txtResult.value = window.parent.calcFactorial(document.form1.txtNum1.value); } catch(exception) { if (typeof(exception) == "string" && exception.charAt(0) == "!") { alert(exception.substr(1)); document.form1.txtNum1.focus(); document.form1.txtNum1.select(); } else { throw exception; } } } catch(exception) { switch (exception) { case "This page is not loaded within the correct frameset": alert(exception); break; default : alert("The following critical error has occurred \n" + exception); } } }
The inner try...catch deals with user input errors. However, if it's not a user input error thrown by us, the error is thrown for the outer catch statement to deal with. The outer catch statement has a switch statement that checks the value of the error message thrown. If it's the error message thrown by us because the calcFactorialTopFrame.htm is not loaded, it deals with it in the first case statement. Any other error is dealt with in the default statement. However, there may well be occasions when there are lots of different errors you want to deal with in case statements.
The try...catch statement has a finally clause that defines a block of script that will execute whether or not an exception was thrown. The finally clause can't appear on its own; it must be after a try block, which the following code demonstrates.
try { ablurt("An exception will occur"); } catch(exception) { alert("Exception occurred"); } finally { alert("Whatever happens this line will execute"); }
The finally part is a good place to put any clean-up code that needs to be executed regardless of any errors that occurred previously.