Exception Handling

When a function or method is called, the only mechanism described to date for passing information back to the script fragment that called the function of method is the return statement. What if something goes wrong? This example illustrates:

function add_like_a_child(num1, num2) {
  if ( num1 + num2 > 20 )
    return "finger & toe limit passed!"
  if ( num1 + num2 > 10 )
    return "finger limit passed!"
  return num1 + num2; 
}

The problem with this function is that every time it gets used, additional code is required to check the return value. One never knows until after that check whether the function has failed to work usefully or not. In this case, you may have to do two checks, one for each unusual case. If the function was complex, you may have to do many checks. How tedious. Here's an example of using this function:

var result = add_like_a_child(3, 5)
if ( result == "finger & toe limit passed!") {
  // do something to cope …
}
else if ( result == "finger limit passed!" ) {
  // do something else to cope …
}
// carry on normally if we reach this point.

In Java, this kind of difficult case is handled with exceptions. The general goal of exceptions is to provide a mechanism that reports when the extraordinary has happened. With such a mechanism in place, one can rely on return values reporting only the ordinary – normal, successful output from the function. With the extraordinary taken care of, there's no need for special, extra checks of the function's return value.

To a degree, JavaScript tries to mimic Java in its syntax. Exceptions are a proposed 'ECMAScript 2' enhancement to the language and appear in both Microsoft JScript 5.0 and JavaScript 1.4. They are likely to become a much-used feature of JavaScript in the future. The reason for this is based on the imperative that if an object is to be used in a script, the scriptwriter must have available access points where he can get at that object. These access points combined make up the object's interface, or signature. In computer language terms, the big three features of object interfaces are properties (also called attributes), methods and exceptions. Hence there are exceptions in JavaScript.

In Java you have to rigidly declare the type of thing that an exception is. True to form, in JavaScript your exception can be any old thing. Here's the official syntax for the JavaScript statements supporting exceptions. First, to create an exception:

throw expression;

Secondly, to handle an exception:

try
  statement-block
catch ( identifier )                // variant1, can be repeated
  statement block
catch ( identifier if condition )   // variant2, can be repeated
  statement block
finally                             // optional, at most one.
  statement block

Here is the example function above re-written:

function add_like_a_child(num1, num2) {
  if ( num1 + num2 > 20 )
    throw "finger & toe limit passed!"
  if ( num1 + num2 > 10 )
    throw "finger limit passed!"
  return num1 + num2; 
}

In this function, if a throw statement is ever executed, processing of the function stops, and the function returns immediately. No ordinary return value is returned. Instead, an exception is returned.

Here is the calling code rewritten:

var result;
try {
  result=add_like_a_child(3,5);
  if ( result == 8 ) {
    // give chocolate
  }
  else {
    // give Brussels sprouts
  }
}
catch (error) {
  if ( error == "finger & toe limit passed!" ) {
 

     // do something to cope
  }
  if ( error == "finger limit passed!" ) {
     // do something else to cope
  }
}

In this script, the result variable might never be set in the third line. If the function returns an exception, processing immediately jumps straight to the catch statement, which has access to the exception in the error variable, and the statements in the block following the catch statement are executed instead. If there is no exception, and the function returns normally, then processing continues with the next statement in the try block, and when that block is finished, the catch block is stepped over entirely, similar to an unused branch of an if statement.

The whole explanation gets a lot easier once you know a little jargon, so here you go. If something goes wrong, you say the function throws an exception. The function is called in a try block and exceptions are handled in catch blocks. The finally block is executed if no exception is outstanding at the end.

The rules for how this complicated processing all works are as follows:

If a throw statement is reached in a function (or in any block-like piece of code), then that function will not continue. It will not return a value, and it will not return void. Instead, it will cease immediately and the exception will be created. In simplistic terms, the exception appears effectively by magic.

If the statement or function causing the exception is not inside a try block, then a runtime interpreter error will occur and the script will stop. If the statement or function is called inside another function, method or block, then that collection of statements will be aborted as well and the exception passed up to the next level of control until a try block is found, or an error results, as before.

If the statement or function causing an exception is inside a try block, then any further statements in the try block are ignored. The interpreter looks through any catch blocks that might be present. If one is found with satisfactory criteria, that single catch block is executed. If any exception remains, because no catch block has satisfactory criteria, or there are no catch blocks, or a catch block raised a new exception, then the finally clause is avoided. Otherwise, all is in order and the finally clause is executed.

What are catch block criteria? There are two cases, illustrated here:

catch (stuff) { ... }

and

catch (stuff if (stuff  > "Error 1")) { ... }

In the first case, the catch block matches all exceptions. In the second case, the catch block only matches those exceptions that pass the if criteria. If there are multiple catch blocks, then the first case will catch everything, much like the default: clause of a switch statement, so it makes sense to put it last in the list of catch blocks. The stuff variable tracks the exception, and it acts within the catch block like a function parameter. The catch block treats the variable stuff in much the same way that this further script treats the variable stuff2:

function something(stuff2) {
…
}

Exception Syntax vs 'if'

If you've never used exceptions before, you're likely to be very skeptical about the syntax arrangements required to get them working by now.

Common objections to the above example are:

For simple cases, it must be admitted that the argument for exceptions is weak; the exception-style code can definitely be bigger. More generally, the argument for using exception syntax goes like this:

It must be admitted that you don't always need exceptions. Simple scripts that just perform sequences of guaranteed operations have no need of exceptions at all, really.

Here is an example that shows exception syntax working for the scriptwriter rather than against:

// piece of code that could fail 20 different ways
try {
  // might cause 1 of 5 exceptions
   dodgy_task_1();
  // might cause 1 of 4 exceptions
  dodgy_task_2();
  // might cause 1 of 7 exceptions
  dodgy_task_3();
 

  // might cause 1 of 4 exceptions
  dodgy_task_4();
}
catch (everything) {
   // scream blue murder
}

Because a throw statement immediately aborts the try block, each dodgy function can come straight after the other, confident that if something goes wrong, the latter ones will never be executed. We've saved a heap of if tests by using the try block in this case. Note that functions needn't have a return value (other than the default of void) in order to make use of exceptions.

Finally, you needn't restrict yourself to throwing primitive types. Here's some paired code that illustrates:

// throws an object
throw { err_string:"You're doomed", err_number: 23 };

This statement creates a two-property object and returns that as the thrown item.

// catches only error number 23
catch ( e if ( e.err_number == 23) ) { … }

This statement expects the exception to be an object with an err_number property, which clearly will be the case if the previous throw statement is responsible for the exception.

Exceptions vs Events vs Errors

If you've messed around with JavaScript in browsers a little before reading this book, then you might be aware that there exist triggers or events that can make JavaScript script fragments run, especially if a given web page contains forms. Are these exceptions or not?

These kinds of events are not generally considered exceptions. Events, generally occurring as a form of input from a user or a network, are expected, planned for, and normal occurrences. They are usually handled the same as normal data. Exceptions are not considered normal, and are usually used for error conditions – something went wrong that the script has been able to anticipate (with try/catch blocks), but can't really handle normally.

Sometimes it's easy to relate events and exceptions together. This is because the way a script gets ready for events is typically a little different to the way it receives plainer kinds of user input. This oddity of handling events can seem similar to the oddity of handling exceptions. Nevertheless, they are separate and different. Combination of HTML Forms and Data gives a flavor of what ordinary events are like, and if you look there, you'll see there's not a mention of JavaScript exception syntax anywhere.

Exceptions Make Sense for Host Objects

A further reason for exception handling support in JavaScript is to be found in host objects. Without host objects JavaScript is nearly useless. What if those host objects generate exceptions as part of their attribute/method/exception interface? There must be a mechanism in place in JavaScript to support this kind of communication. This is especially the case if JavaScript is to work closely with Java, which has support for exceptions deeply ingrained in its language.

Exceptions Make Sense for Interpreter Errors

Some things that go wrong with JavaScript scripts can be pretty bad. If the scriptwriter has made a typographical error in some obscure part of a large script, it might never be detected until that script is installed in some critical place. That is the risk you take with an interpreted language.

If the interpreter comes to read the script and finds a typographical error, wouldn't it be nice if there were something that could be done about it rather than the whole script just stopping dead? This is a future direction for the ECMAScript standard – scripting errors might be reported as exceptions and handled by the system that starts the JavaScript interpreter in the first place.

eval() Concepts

Interpreted languages like JavaScript have a very useful peculiarity that compiled languages lack. Because the interpreter exists as part of the program that runs the script, the possibility exists that more interpretation of JavaScript statements can occur after the script has been read and started. We might call this dynamic or run-time evaluation to emphasize that it happens after the script gets going. Sometimes this might be called piggyback embedding.

Simple Dynamic Evaluation – Maps

Remember how objects and arrays have interchangeable syntax. Recall that these two statements are equivalent:

object.property = "stuff"
array["property"] = "stuff"

Because the argument supplied to the array is an expression, it's possible to do handy things like this:

var all_cars = new CarCollection;
var car;
while ( (car=get_car_name()) != "" )
  all_cars[car.toLowerCase()] = get_registration(car);

In the last line, the variable car is used to create the property names of the all_cars object so that one property matches each car in the collection. Then each of those properties is set to a related value for that car – in this case the registration number. So if these collectable cars existed:

Model T Ford	A234
Goggomobile	MCI 223
Austin Lancer	JG 33

Then the properties of the all_cars object would be "model t ford", "goggomobile" and "austin lancer". Later we could refer to those properties as follows to dig out the related information:

var x = "model t ford";
all_cars[x];                  // "A234"
all_cars.goggomobile;         // "MCI 233"
all_cars["austin lancer"];    // "JG 33"

Effectively, the properties of the object depend on the data available, and that might not be known until runtime (if the user enters it, for example).

This ability to interchange between data and variable names is a powerful and common feature of scripting languages. In particular, this example shows a very commonly used structure dubbed alternatively a map, mapping, associative array, hash, or merely dictionary. The point is that it lets you get at a data value (a property's value) based on a unique key value (the property name), and it lets you keep a bunch of such pairs neatly together. It is a simple form of unordered index. Mathematicians say "There is a mapping from the set of collectable cars to the set of registration numbers for those cars". Whatever.

Note that in this example we'd be in trouble if a second Model T Ford turned up. That second car would have the same property name as the first and the system breaks down. That's why this approach is nearly always used for data that has a set of unique values only. Plenty of data is like that.

Turning Script Statements Into Data

Bits of JavaScript can be very simply turned back into data if you choose the right bits. Here are a couple of examples:

var greatgranddad = new Patriach;
greatgranddad.child1 = new Granddad;
// ...
// lots more family tree detail
// ...
var tree=greatgranddad.toString();  // If you specify JavaScript1.2
print_to_screen(tree);

In this example, a hierarchy of objects is created starting with Great-Granddad who clearly is the most senior. When the toString() method is called, the object is forced into a String type, and if you're lucky (because the different interpreter brands are a little varied in this functionality still) what will be printed to the screen will be a literal object matching the object's contents. In this case the literal would start:

{ child1:{ …

Note, however, that this functionality of toString() is only available if you specify LANGUAGE = "JavaScript1.2" when writing it. In JavaScript 1.3 and beyond (NC4.06+, IE5+), this functionality is taken on by toSource().

A similar trick is possible with functions:

function first() { … }
function second() { … }
function third() { … }

function do_it() {
  first() || throw first.toString();
  second() || throw second.toString();
  third() || throw third.toString();
}

try { do_it(); }
catch ( e ) { alert('failed: ' + e) }

In this example, you might be testing 3 functions for syntax problems. If any of them fail, returning false, their source code is returned as an exception, and the user (presumably) is shown the offending code via the alert() function in the catch block. Recall Microsoft's JScript 5.0 is the only implemented contender at this time of writing for try and catch statements.

In both these cases, the toString()/toSource() method is not always required – recall that there is automatic type conversion in JavaScript.

This process won't necessarily work everywhere. For example, JavaScript statements that aren't part of any function are harder to get back as data. The JavaScript interpreter in Internet Explorer 4.0 is a notable exception.

The Mighty eval()

The native function eval() is by far the most powerful native function in JavaScript, allowing a JavaScript string to be examined, interpreted and run as though it was a script.

var x=5, y=6, result=0;
var formula = "result = x*y;";
eval(formula);                     // result is now 30

The contents of the formula variable can be as complicated as you like – multiple statements, if-else logic, and so on. Provided the string contains valid JavaScript, everything works fine. However, if the string contains invalid JavaScript, the interpreter will give a runtime error and halt. Because the script fragment is hidden inside a string, the interpreter has no chance of detecting problems with it when it first reads the whole script. That means that syntax checks that would otherwise be performed at script startup time are delayed until the eval() statement is actually executed. That means you must take extreme care when using eval(), or else everything can come crashing down much later on, when you least expect it. Perhaps by the time this book is in your hands there will exist an interpreter that issues an exception instead.

Here's a more complicated example of eval(). This example runs a different function depending on whether the boss is around or not:

var type1 = "function work() { slog(); slog(); slog(); }";
var type2 = "function work() { rest(); rest(); rest(); }";

if ( boss ) 
  eval(type1);
else
  eval(type2);
eval("work()");

Notice how you can delay defining any kind of work at all until the boss is around. Handy.

eval() has the potential to be used pretty powerfully. Suppose you have an application that hosts a JavaScript interpreter. Suppose that application connects to another application, possibly on a remote computer that also hosts a JavaScript interpreter. A script in the local application could turn some script functions back into data using toSource(), and then send them to the remote application via a convenient host object. The remote application could then eval() the data, turning it back into scripts. This kind of architecture allows scripts to move around a computer network and execute in different places as "mobile agents", a very modern and fluid approach.

Basically this technique works anywhere you can store interpretable code in a string and execute it. Macintosh users may recognize that HyperCard does the same with the do command. In a Bourne shell window, you can write text out to a file with an echo statement, chmod it to make it executable and 'dot run' it into the current process space to achieve the same goal.

Piggyback embedding

Let's look at the way an eval() may help us out with some interesting problems.

Within the context of distributed JavaScript, you can put a function in a string, send the string to a script on another computer which then does an eval(), executes it, gets an answer, and passes the answer, the function, or a modified version of the function back to the sending computer, or possibly to another computer. So facilities that only really exist on one computer can be executed on request from a remote master machine.

The piggybacking comes from the way you carry one script around in another. Technically there is no limit to the number of enclosures within enclosures. The scripts can transcend languages and be embedded in completely foreign environments. For example, you could transfer a Bourne shell script inside a JavaScript script that was transferred across the network from a series of strings that were constructed in a source script written in Perl.

To construct these mechanisms you basically work backwards from the ultimate target environment, wrapping the scripts with increasing numbers of onion skin layers as you go.