ActionScript.org Flash, Flex and ActionScript Resources - http://www.actionscript.org/resources
Exceptions and Exception Handling
http://www.actionscript.org/resources/articles/603/1/Exceptions-and-Exception-Handling/Page1.html
Colin Moock
Colin Moock is an independent web guru with a passion for networked creativity and expression. He has been researching, designing, and developing for the Web since 1995. Colin served as webmaster for SoftQuad, Inc. (makers of HoTMetaL PRO) until 1997, and then as web evangelist for ICE (one of Canada's leading interactive agencies) until 2001. He has created interactive content for Sony, Levi's, Nortel, Air Canada, Procter & Gamble, and Hewlett-Packard. Colin now divides his time between writing, speaking at conferences, and researching emerging web technology. His award-winning Flash work and his renowned support site for Flash developers (http://www.moock.org) have made him a well-known personality in the Flash developer community. He is a contributor to http://macromedia.com's Flash developer center, a tutorialist in the Flash MX Bible (2002, Wiley Publishing Inc.), and regularly appears in industry magazines such as cre@te! online. Colin's latest personal undertaking is Unity, a Flash socket server for multi-user content.  
By Colin Moock
Published on May 18, 2007
 
This full chapter excerpted from Colin Moock’s acclaimed “Essential ActionScript 2.0” covers exceptions and exception management through ActionScript in great detail. Ideal for the intermediate coder looking to increase the professionalism of their code, or save themselves hours of debugging.

Introduction

Copyright © 2004 O'Reilly Media, Inc. All Rights Reserved.
Essential ActionScript 2.0
By Colin Moock
June 2004
ISBN: 0-596-00652-7
More info... .
Available from booksellers or direct from O'Reilly Media, www.oreilly.com.

Cover image
This content is excerpted from the above-named O'Reilly publication, with permission, by agreement with ActionScript.org.

Chapter 10. Exceptions

Table of Contents

10.1. The Exception-Handling Cycle
10.2. Handling Multiple Types of Exceptions
10.2.1. Determining Exception Type Granularity
10.3. Exception Bubbling
10.3.1. Uncaught Exceptions
10.4. The finally Block
10.5. Nested Exceptions
10.5.1. A Nested Exception Bug
10.6. Control Flow Changes in try/catch/finally
10.7. Limitations of Exception Handling in ActionScript 2.0
10.7.1. No Checked Exceptions
10.7.2. No Built-in Exceptions
10.7.3. Exception Performance Issues
10.8. From Concepts to Code

Throughout this book, we've encountered plenty of compile-time errors—errors reported in the Output panel when a movie is exported. If a compile-time error occurs in an ActionScript 2.0 program, compilation fails and Flash won't generate a .swf file to execute. But not all ActionScript errors occur at compile time. Some errors don't occur until runtime, and they may not cause the program to fail completely. For example, suppose we attempt to load an XML file from disk, but the file is not found. If the movie is running in Test Movie mode, the failed load causes the Output panel to display an error message—but the movie continues to run. The following code demonstrates:

[as]// If the specified file doesn't exist...
var xmlDoc:XML = new XML( );
xmlDoc.load("http://www.somenonexistentsite.com/someNonExistentFile.xml");[/as]

[code]// ...the Output panel displays:
Error opening URL
"http://www.somenonexistentsite.com/someNonExistentFile.xml"[/code]

In an ideal world, we'd like to be able to recover from nonfatal error conditions such as a file-not-found. We'd like to tell the user there was a problem loading the file and perhaps display the problematic filename.

Unfortunately, in ActionScript there are precious few built-in runtime errors and, what's worse, there's no standard error-handling mechanism for dealing with the errors that do occur at runtime—at least, not for the errors that are generated by ActionScript itself. Most errors in ActionScript occur in the form of custom error codes and return values. For example, a method might return the value -1, false, or null to indicate that some operation failed. This requires us to write different, individualized code for each kind of error generated by ActionScript.

Luckily, the situation is not so bleak for our own code. As of Flash Player 7, we can write code that generates standardized errors via the throw statement. We handle those errors via the try/catch/finally statement. (The throw and try/catch/finally statement syntax and behavior are borrowed from Java and C++.)

Tip

The vast majority of ActionScript 2.0 is backward compatible with Flash Player 6. However, the exception-handling features discussed in this chapter require Flash Player 7 to work. Hence, outside this chapter, code examples in this book do not use exception handling. This allows nearly all examples to run cheerfully in Flash Player 6.

The Exception-Handling Cycle

To learn how to dispatch and respond to errors in our own programs, we'll return to the Box class example from Chapter 4. Recall that in the Box class, we defined a setWidth( ) method to set the width property of a Box instance. The setWidth( ) method checks whether a new width is within legal range before changing the width property of the Box instance. If the new width specified is not valid, no change is made, and the method signals a problem by returning the value false. The relevant Box class code follows. (Note that the Box class code in this example is incomplete; for now we're concentrating only on the portions of the class that deal with setting the width property.)

[as]class Box {
private var width:Number;

public function setWidth (w:Number):Boolean {
if ((isNaN(w) || w == null) || (w <= 0 || w > Number.MAX_VALUE)) {
// Invalid data, so quit.
return false;
}
width = w;
return true;
}
}[/as]

When setWidth( ) returns the value false, it does so to indicate an error condition. Code that invokes the setWidth( ) method is expected to check setWidth( )'s return value and respond gracefully if there's a problem (i.e., if setWidth( ) returns false).

Let's now revise the setWidth( ) method so that it generates an exception, a standard form of error supported directly by ActionScript 2.0. Error dispatch and recovery with exceptions works in much the same way as the preceding setWidth( ) method; when a problem occurs, an error is signaled, and some error-recovery code is expected to handle it.

To generate an exception (i.e., signal an error) in our code, we use the throw statement, which takes the following form:

throw expression

where expression is a data value that describes the unusual or problematic situation. Using throw to signal an error is known as "throwing an exception." ActionScript allows any value to act as the expression of a throw statement. For example, the expression value could be the string literal "Something went wrong!" or it could be a numeric error code. However, the best practice is for expression to be an instance of the Error class (or an instance of a subclass of Error).

The Error class, introduced as a built-in class in Flash Player 7, is a standard class for representing error conditions. Instances of the Error class represent an error (a.k.a. an exception) in a program.

Tip

The Error class defines two public properties, name and message , used to describe the error. The Error class also defines a single method, toString( ), which returns the value of message or, if message is not defined, returns the string "Error."


Basic Exception Handling Syntax

Copyright © 2004 O'Reilly Media, Inc. All Rights Reserved.
Essential ActionScript 2.0
By Colin Moock
June 2004
ISBN: 0-596-00652-7
More info... .
Available from booksellers or direct from O'Reilly Media, www.oreilly.com.

Cover image
This content is excerpted from the above-named O'Reilly publication, with permission, by agreement with ActionScript.org.

Here's one way to write setWidth( ) so that it generates an exception with a throw statement when an invalid width is received:

[as]public function setWidth (w:Number):Void {
if ((isNaN(w) || w == null) || (w <= 0 || w > Number.MAX_VALUE)) {
throw new Error("Invalid width specified.");
}
width = w;
}[/as]

When the throw statement executes, program control is immediately transferred to a special section of code that knows how to deal with the problem (we'll discuss this shortly). In official terms, the code that deals with the problem is said to handle the exception.

In our revised setWidth( ) method, when the value of w is illegal, we use throw to halt the method, and we pass a new Error object out of the method to the section of code (not yet shown) that will handle the problem:

[as]throw new Error("Invalid width specified.");[/as]

We also supply a description of the problem—"Invalid width specified"—as an argument to the Error constructor. The code block that handles the exception (again, not yet shown) uses the Error instance to diagnose what went wrong. Notice that setWidth( ) no longer returns a Boolean success or failure value. If the method encounters a problem, it uses the throw statement to both quit and signal a failure. Otherwise, it completes normally and we can rest assured that it performed its job successfully.

Now that our setWidth( ) method includes the throw statement (i.e., now that it might generate an exception), we must adjust the way we invoke it. Previously, we would have used the return value of setWidth( ) to determine what to do in the event of a problem:

[as]var b:Box = new Box( );
var someWidth:Number = -10;

// Check setWidth( )'s return value...
if (b.setWidth(someWidth)) {
// ...setWidth( ) returned true, so no problems; proceed as planned.
trace("Width set successfully.");
} else {
// ...setWidth( ) returned false! ERROR! Invalid data. Display a warning.
trace("An error occurred.");
}[/as]

But now, when invoking the new exception-based version of our method, we don't bother checking the return value of setWidth( ). Instead, we set up a code branch using the formal try/catch/finally statement instead of an if/else statement. Here's how the new version looks:

[as]var b:Box = new Box( );
var someWidth:Number = -10;

try {
b.setWidth(someWidth);
// If we get this far, no exception occurred; proceed as planned.
trace("Width set successfully.");
} catch (e:Error) {
// ERROR! Invalid data. Display a warning.
trace("An error occurred: " + e.message);
}[/as]

Let's study the preceding code in closer detail. The try keyword tells the interpreter that we're about to execute some code that might generate an exception:

[as]try {
// Code here might cause an exception.
}[/as]

In this case, the code we're executing is setWidth( ):

[as]try {
b.setWidth(someWidth);
// If we get this far, no exception occurred; proceed as planned.
trace("Width set successfully.");
}[/as]

The catch block handles exceptions generated by the try block. That is, the code in the catch block executes if, and only if, code in the try block generates an exception:

[as]} catch (e:Error) {
// ERROR! Invalid data. Display a warning.
trace("An error occurred: " + e.message);
}[/as]

When we invoke b.setWidth( ) within the try block, if setWidth( )'s throw statement doesn't execute (i.e., if no error occurs), then the subsequent statements in the try block execute normally and the program skips the catch block entirely. But if setWidth( ) throws an exception, the program immediately skips to and executes the catch block.

Notice, therefore, the typical structure:

  • Code in a try clause invokes a function that might throw an exception.

  • Code in the invoked function throws an exception using the throw statement if an error occurs.

  • Control returns either to the try block (if no error is thrown) or the catch block (if an error is thrown). The catch block deals with any errors that occur in the try block.

When the catch block is executed, it receives the expression of the throw statement as a parameter. In our present example, within the catch block, the parameter e stores the Error instance created by the throw statement in the Box.setWidth( ) method:

// Here's the throw statement, excerpted from setWidth( ).
throw new Error("Invalid width specified.");

We can use that Error instance to help diagnose the problem. The string passed to the Error constructor is available via the message property of the Error instance. In our example catch statement, we simply display the Error instance's message in the Output panel, as follows:

[as]trace("An error occurred: " + e.message);[/as]

The Error class's toString( ) method, which is called automatically when an instance is used in a string context, returns the value of the message property. Hence, in a string context, e and e.message are equivalent. For example, the following two statements are synonymous:

[as]trace("An error occurred: " + e.message);
trace("An error occurred: " + e);[/as]

We'll use them interchangeably throughout this chapter.

Note that the parameter listed in a catch block should not need to be declared as a variable before being used. However, due to a bug in version 7.0 of the Flash MX 2004 authoring tool, the compiler (incorrectly) generates an error when a catch block parameter is referenced without first being declared. For example, in version 7.0 of Flash MX 2004, the following code:

[as]public function someMethod ( ):Void {
try {
throw new Error("Some error message.");
} catch (e:Error) {
// Respond to the error.
trace("An error occurred: " + e);
}
}[/as]

(incorrectly) generates the following error:

There is no property with the name 'e'.

This bug is fixed in the Flash MX 2004 updater, available at: http://macromedia.com/support/flash/downloads.html. (To work around the bug without installing the updater, simply declare the parameter e as a variable.)

Metaphorically, the code that detects a problem throws an exception (passes an Error object) to the catch block, which receives it as a parameter (catches it).

Incidentally, the try block can throw an error directly. For example, in the following code, the catch block is executed when x divided by y is the numeric value NaN (as is the case when both x and y are 0):

[as]var x:Number = 0;
var y:Number = 0;
var e:Error; // Declare e as parameter to avoid compiler bug
try {
if (isNaN(x/y)) {
throw new Error("Quotient is NaN.");
} else {
trace ("Result is " + String(x/y));
}
} catch (e:Error) {
trace("Error: " + e.message);
}[/as]

In the preceding example, you might think that attempting to divide by 0 (when y is 0) would cause ActionScript itself to throw a "Division by zero" exception, but no such luck. ActionScript doesn't throw exceptions. It is up to the developer to check for error conditions and invoke throw as desired. Furthermore, in ActionScript, dividing anything other than 0 by 0 yields Infinity (for positive numerators) or -Infinity (for negative numerators).

Whatever the case, it's more common for try blocks to invoke methods that throw exceptions than for a try block to include a throw statement directly. Later, under "Exception Bubbling," we'll learn more about how errors are transferred from methods to enclosing try blocks. For now, you can simply rely on the rules in the following tip.

Tip

Within a try block, if a statement executes, you can safely trust that all preceding statements have executed successfully. If code in a try block (or a method invoked in the try block) throws an error, the remaining statements in the try block are skipped and the statements in the catch block are executed. If no exception is thrown, the try block completes and execution resumes with the statements immediately following the try/catch/finally statement.

To find out what happens if the error is never caught (or, synonymously, never trapped), see "Uncaught Exceptions," later in this chapter.


Handling Multiple Types of Exceptions

Copyright © 2004 O'Reilly Media, Inc. All Rights Reserved.
Essential ActionScript 2.0
By Colin Moock
June 2004
ISBN: 0-596-00652-7
More info... .
Available from booksellers or direct from O'Reilly Media, www.oreilly.com.

Cover image
This content is excerpted from the above-named O'Reilly publication, with permission, by agreement with ActionScript.org.

Our first exception example was overly simplistic. What happens if our method generates more than one kind of error? Are they all sent to the same catch block? Well, that's up to the developer; they certainly could be, but it's more typical and better practice for different kinds of errors to be handled by separate catch blocks. Let's examine why.

Suppose we wanted a finer-grained set of error messages in our setWidth( ) method: one for general invalid data, one for too small a width, and one for too large a width. Our revised setWidth( ) method might look like this:

[as]public function setWidth (w:Number):Void {
if (isNaN(w) || w == null) {
throw new Error("Illegal Box dimension specified.");
} else if (w <= 0) {
throw new Error("Box dimensions must be greater than 0.");
} else if (w > Number.MAX_VALUE) {
throw new Error("Box dimensions must be less than Number.MAX_VALUE.");
}
width = w;
}[/as]

To handle the three possible error messages in our new setWidth( ) message, we might be tempted to code our try/catch/finally statement as follows:

[as]try {
b.setWidth(someWidth);
// If we get this far, no exception occurred; proceed as planned.
trace("Width set successfully.");
} catch (e:Error) {
switch (e.message) {
case "Illegal Box dimension specified.":
trace("An error occurred: " + e.message);
trace("Please specify a valid dimension.");
break;

case "Box dimensions must be greater than 0.":
trace("An error occurred: " + e.message);
trace("Please specify a larger value.");
break;

case "Box dimensions must be less than Number.MAX_VALUE.":
trace("An error occurred: " + e.message);
trace("Please specify a smaller value.");
break;
}
}[/as]

Admittedly, that code does work, but it's fraught with problems. First, and most serious, the errors are distinguished from one another only by the text in a string that is hidden within the Box class. Each time we want to check the type of an error, we have to look inside the Box class and find the message string. Using the string for error identification across multiple methods and classes is highly prone to human error and makes our code difficult to maintain. Second, the switch statement itself is barely more readable than our original, if/else statement (the one we used before we added exceptions to the setWidth( ) method). We're not much farther ahead than we would be if we had used, say, numeric error codes instead of formal exceptions.

Fortunately, there's a formal (and elegant) way to handle multiple exception types. Each try block can have any number of supporting catch blocks. When an exception is thrown in a try block that has multiple catch blocks, the interpreter executes the catch block whose parameter's datatype matches the datatype of the value originally thrown. The datatypes of the catch parameter and thrown value are considered a match if they are identical or if the catch parameter type is a superclass of the thrown value's type.

Here's the general syntax of a try statement with multiple catch blocks:

[as]try {
// Code that might generate an exception.
} catch (e:ErrorType1) {
// Error-handling code for ErrorType1.
} catch (e:ErrorType2) {
// Error-handling code for ErrorType2.
} catch (e:ErrorTypen) {
// Error-handling code for ErrorTypen.
}[/as]

If a throw statement in the preceding try block were to throw an expression of type ErrorType1, then the first catch block would be executed. For example, the following code causes the first catch block to execute:

[as]throw new ErrorType1( );[/as]

If a throw statement were to pass an expression of type ErrorType2, then the second catch clause would be executed, and so on. As we learned earlier, in ActionScript the throw statement expression can belong to any datatype. However, as an OOP best practice, we should throw only instances of the Error class or one of its subclasses (this best practice follows Java, where throw can be used only with Throwable and its subclasses).

If we want to throw multiple kinds of exceptions in an application, we should define an Error sub" name="as2e-CHP-10-ITERM-3455">granularity you require (i.e., to what degree you need to differentiate among different error conditions). However, don't confuse the following discussion of how to implement granularity in error handling as an insistence that you must implement such granularity.



Determining Exception Type Granularity

Copyright © 2004 O'Reilly Media, Inc. All Rights Reserved.
Essential ActionScript 2.0
By Colin Moock
June 2004
ISBN: 0-596-00652-7
More info... .
Available from booksellers or direct from O'Reilly Media, www.oreilly.com.

Cover image
This content is excerpted from the above-named O'Reilly publication, with permission, by agreement with ActionScript.org.

Should you define an Error subclass for each error condition? Typically, no, you won't need that level of granularity because in many cases multiple error conditions can be treated in the same way. If you don't need to differentiate among multiple error conditions, you can group them together under a single custom Error subclass. For example, you might define a single Error subclass named InvalidInputException to handle a wide range of input problems.

That said, you should define a separate Error subclass for each error condition that you want to distinguish from other possible conditions. To help you understand when you should create a new subclass for a given error condition and to demonstrate how to group multiple conditions into a single subclass, let's return to our familiar Box class.

Earlier we generated three exceptions from the Box.setWidth( ) method: one for general invalid data, one for too small a width, and one for too large a width. All three Box-related exceptions used the generic Error class. Here's the code again:

[as]public function setWidth (w:Number):Void {
if (isNaN(w) || w == null) {
throw new Error("Illegal Box dimension specified.");
} else if (w <= 0) {
throw new Error("Box dimensions must be greater than 0.");
} else if (w > Number.MAX_VALUE) {
throw new Error("Box dimensions must be less than Number.MAX_VALUE.");
}
width = w;
}[/as]

In the preceding code, to differentiate Box exceptions from all other exceptions in our application, we use the Error class's message property, which, as we just learned, made our exceptions awkward to use and prone to human error. A better way to set Box-related data errors apart from other errors in our application is to define a custom Error subclass, BoxDimensionException, as follows:

[as]// Code in BoxDimensionException.as:
class BoxDimensionException extends Error {
public var message:String = "Illegal Box dimension specified.";
}[/as]

With our BoxDimensionException class in place, our Box.setWidth( ) method can throw its very own type of error, as follows:

public function setWidth (w:Number):Void {
if (isNaN(w) || w == null) {
// Throw a BoxDimensionException instead of an Error.
throw new BoxDimensionException( );
} else if (w <= 0) {
throw new BoxDimensionException( );
} else if (w > Number.MAX_VALUE) {
throw new BoxDimensionException( );
}
width = w;
}

Notice that the preceding method definition throws the same error type (BoxDimensionException) for all three Box-related error conditions. As developers of the Box class, we now face the crux of the error granularity issue. We must decide not only how distinguishable we want Box error messages to be from other application errors, but also how distinguishable we want those errors to be from one another. We have the following options:

Option 1: use a single Box error class.

Leave the preceding setWidth( ) method definition as-is. As we'll see shortly, this option lets us distinguish Box errors from other generic errors in the program, but it does not help us distinguish internally among the three varieties of Box-related errors (invalid data, too small a width, and too large a width).

Option 2: simplify code, but still use a single Box error class.

Refactor the setWidth( ) method to check for all three error conditions using a single if statement. This option is the same as the previous option, but uses cleaner code.

Option 3: use debugging messages to distinguish among errors.

Add configurable debugging messages to the BoxDimensionException class. This option adds slightly more granularity than the previous two options but only for the sake of the developer and only during debugging.

Option 4: create a custom exception class for each error condition.

Create two custom BoxDimensionException subclasses, BoxUnderZeroException and BoxOverflowException. This option provides the most granularity—it lets a program respond independently to the three varieties of Box-related errors using formal branching logic.

Let's consider the preceding options in turn.



Using a single custom exception type

Copyright © 2004 O'Reilly Media, Inc. All Rights Reserved.
Essential ActionScript 2.0
By Colin Moock
June 2004
ISBN: 0-596-00652-7
More info... .
Available from booksellers or direct from O'Reilly Media, www.oreilly.com.

Cover image
This content is excerpted from the above-named O'Reilly publication, with permission, by agreement with ActionScript.org.

Our first option is to accept the preceding setWidth( ) definition, which throws the same error type (BoxDimensionException) for all three Box-related error conditions. Because the method uses BoxDimensionException and not Error to throw exceptions, Box exceptions are already distinguishable from other generic exceptions. Users of the setWidth( ) method can use code such as the following to discriminate between Box-related errors and other generic errors:

[as]var b:Box = new Box( );
var someWidth:Number = -10;

try {
// This call to setWidth( ) will generate a BoxDimensionException.
b.setWidth(someWidth);
// Other statements in this try block might generate other generic errors.
// For demonstration purposes, we'll throw a generic error directly.
throw new Error("A generic error.");
} catch (e:BoxDimensionException) {
// Handle Box dimension errors here.
trace("An error occurred: " + e.message);
trace("Please specify a valid dimension.");
} catch (e:Error) {
// Handle all other errors here.
trace("An error occurred: " + e.message);
}[/as]

For many applications, the level of error granularity provided by BoxDimensionException is enough. In such a case, we should at least refactor the setWidth( ) method so that it doesn't contain redundant code (throwing the BoxDimensionException three times). Here's the refactored code (which was option 2 in our earlier list):

[as]public function setWidth (w:Number):Void {
if ((isNaN(w) || w == null) || (w <= 0) || (w > Number.MAX_VALUE)) {
throw new BoxDimensionException( );
}
width = w;
}[/as]

Using configurable debugging messages

Now let's turn to option 3 (adding configurable debugging messages to the BoxDimensionException class). Options 1 and 2 let us distinguish a Box exception from other exceptions in the application but didn't help us distinguish, say, an overflow exception from a less-than-zero exception. If we feel that it's difficult to debug a Box dimension problem without knowing whether a box is too big or too small, we can adjust the BoxDimensionException class so that it accepts an optional description (the equivalent of a proverbial "note to self"). Here's the adjusted BoxDimensionException class:

[as]class BoxDimensionException extends Error {
// The default error message still stands.
public var message:String = "Illegal Box dimension specified.";

// Provide a constructor that allows a custom message to be supplied.
public function BoxDimensionException (boxErrMsg:String) {
super(boxErrMsg);
}
}[/as]

To make use of our adjusted BoxDimensionException class in setWidth( ), we revert to our setWidth( ) code used in option 1 and add debugging error messages, as follows:

[as]public function setWidth (w:Number):Void {
if (isNaN(w) || w == null) {
// The default error message is fine in this case,
// so don't bother specifying a custom error message.
throw new BoxDimensionException( );
} else if (w <= 0) {
// Here's the custom "too small" error message.
throw new BoxDimensionException("Box dimensions must "
+ "be greater than 0.");
} else if (w > Number.MAX_VALUE) {
// Here's the custom "too big" error message.
throw new BoxDimensionException("Box dimensions must be less "
+ "than Number.MAX_VALUE.");
}
width = w;
}[/as]

Now that setWidth( ) supplies custom error messages, we'll have an easier time debugging a Box problem because we'll know more information when the error occurs. Our use of the setWidth( ) method has not changed, but we're better informed when something goes wrong, as shown next:

[as]var b:Box = new Box( );
var someWidth:Number = -10;

try {
b.setWidth(someWidth);
} catch (e:BoxDimensionException) {
// Handle Box dimension errors here.
// In this case, the helpful debugging output is:
// An error occurred: Box dimensions must be greater than 0.
trace("An error occurred: " + e.message);
} catch (e:Error) {
// Handle all other errors here.
trace("An error occurred: " + e.message);
}[/as]



Multiple custom BoxDimensionException subclasses

Copyright © 2004 O'Reilly Media, Inc. All Rights Reserved.
Essential ActionScript 2.0
By Colin Moock
June 2004
ISBN: 0-596-00652-7
More info... .
Available from booksellers or direct from O'Reilly Media, www.oreilly.com.

Cover image
This content is excerpted from the above-named O'Reilly publication, with permission, by agreement with ActionScript.org.

Option 3 (adding configurable debugging messages to the BoxDimensionException class) helped us investigate a problem in our code during development, but it doesn't help the program take independent action to recover from individual Box errors. To allow the program to execute independent code branches based on the type of Box error thrown, we need custom BoxDimensionException subclasses (option 4).

Tip

If you want a program to differentiate among error conditions, implement a separate Error subclass for each one. Don't rely on the Error.message property alone to implement program branching logic. If your custom Error subclass defines a constructor that accepts an error string, you should use that string only for debugging, not for branching logic.

To allow our program to independently differentiate among the Box class's three error conditions, we create three custom exception types by creating three Error subclasses: BoxDimensionException, BoxUnderZeroException, and BoxOverflowException. In our case, the BoxDimensionException class extends Error directly. The BoxUnderZeroException and BoxOverflowException classes both extend BoxDimensionException because we want to differentiate these specific error types from a more general invalid dimension exception. Hence, the datatype hierarchy is shown in Figure 10-1.

Figure 10-1. The custom exception class hierarchy

The custom exception class hierarchy

Here's the source code for our three Box Error subclasses:

[as]// Code in BoxDimensionException.as:
class BoxDimensionException extends Error {
public var message:String = "Illegal Box dimension specified.";
}

// Code in BoxUnderZeroException.as:
class BoxUnderZeroException extends BoxDimensionException {
public var message:String = "Box dimensions must be greater than 0.";
}

// Code in BoxOverflowException.as:
class BoxOverflowException extends BoxDimensionException {
public var message:String = "Box dimensions must be less "
+ "than Number.MAX_VALUE.";
}[/as]

Each class specifies the value of its message property directly and does not allow it to be customized on a per-use basis. Truth be told, now that we're dealing with class-based errors instead of string-based errors, the message property is completely secondary. What matters is the datatype of the exception generated by the throw statement. When catching any of the preceding Box exceptions, our program will use the exception's datatype (not the message property) to distinguish between the three kinds of exceptions.

Now that we have three exception types, let's update our setWidth( ) method to throw those types. Here's the code:

[as]public function setWidth (w:Number):Void {
if (isNaN(w) || w == null) {
throw new BoxDimensionException( );
} else if (w <= 0) {
throw new BoxUnderZeroException( );
} else if (w > Number.MAX_VALUE) {
throw new BoxOverflowException( );
}
width = w;
}[/as]

Notice that we do not pass any error description to the various Box exception constructors. Once again, the description of each exception is set by each custom Error subclass using its message property.


Multiple custom BoxDimensionException subclasses continued

Copyright © 2004 O'Reilly Media, Inc. All Rights Reserved.
Essential ActionScript 2.0
By Colin Moock
June 2004
ISBN: 0-596-00652-7
More info... .
Available from booksellers or direct from O'Reilly Media, www.oreilly.com.

Cover image
This content is excerpted from the above-named O'Reilly publication, with permission, by agreement with ActionScript.org.

With each Box exception represented by its own class, the errors that can be generated by the setWidth( ) method are well-known to programmers working with Box instances. The exception types are visible outside the Box class, exposed appropriately to programmers working on the application. Just by glancing at the application class hierarchy, the programmer can determine the exceptions that relate to the Box class. Furthermore, if the programmer mistakenly uses the wrong name for an exception, the compiler generates a datatype error.

Now let's see how to add branching logic to our code based on the types of exceptions that can be generated by Box.setWidth( ). Pay close attention to the datatype of each catch block parameter:

[as]var b:Box = new Box( );
var someWidth:Number = -10;

try {
b.setWidth(someWidth);
} catch (e:BoxOverflowException) {
// Handle overflow.
trace("An error occurred: " + e.message);
trace("Please specify a smaller value.");
} catch (e:BoxUnderZeroException) {
// Handle under zero.
trace("An error occurred: " + e.message);
trace("Please specify a larger value.");
} catch (e:BoxDimensionException) {
// Handle general dimension errors.
trace("An error occurred: " + e.message);
trace("Please specify a valid dimension.");
}[/as]

In the preceding code, if the setWidth( ) method generates a BoxOverflowException, the first catch block executes. If setWidth( ) generates a BoxUnderZeroException, the second catch block executes. And if setWidth( ) generates a BoxDimensionException, the third catch block executes. Notice that the error datatypes in the catch blocks progress from specific to general. When an exception is thrown, the catch block executed is the first one that matches the datatype of the exception.

Hence, if we changed the datatype of the first catch block parameter to BoxDimensionException, the first catch block would execute for all three kinds of exceptions! (Remember, BoxDimensionException is the superclass of both BoxUnderZeroException and BoxOverflowException, so they are considered matches for the BoxDimensionException datatype.) In fact, we could prevent all of the catch blocks from executing simply by adding a new first catch block with a parameter datatype of Error:

[as]try {
b.setWidth(someWidth);
} catch (e:Error) {
// Handle all errors. No other catch blocks will ever execute.
trace("An error occurred:" + e.message);
trace("The first catch block handled the error.");
} catch (e:BoxOverflowException) {
// Handle overflow.
trace("An error occurred: " + e.message);
trace("Please specify a smaller value.");
} catch (e:BoxUnderZeroException) {
// Handle under zero.
trace("An error occurred: " + e.message);
trace("Please specify a larger value.");
} catch (e:BoxDimensionException) {
// Handle general dimension errors.
trace("An error occurred: " + e.message);
trace("Please specify a valid dimension.");
}[/as]

Obviously, the addition of the first catch clause in the preceding code is self-defeating, but it does illustrate the hierarchical nature of exception handling. By placing a very generic catch block at the beginning of the catch list, we can handle all errors in a single location. Conversely, by placing a very generic catch block at the end of the catch list, we can provide a general safety net that handles any errors not caught by earlier catch blocks. For example, in the following code, the final catch block executes only if the try block generates an exception that doesn't belong to the BoxOverflowException, BoxUnderZeroException, or BoxDimensionException datatypes:

[as]try {
b.setWidth(someWidth);
} catch (e:BoxOverflowException) {
// Handle overflow.
trace("An error occurred: " + e.message);
trace("Please specify a smaller value.");
} catch (e:BoxUnderZeroException) {
// Handle under zero.
trace("An error occurred: " + e.message);
trace("Please specify a larger value.");
} catch (e:BoxDimensionException) {
// Handle general dimension errors.
trace("An error occurred: " + e.message);
trace("Please specify a valid dimension.");
} catch (e:Error) {
// Handle any errors that don't qualify as BoxDimensionException errors.
}[/as]

Remember, error granularity is a choice. In option 4 we created a custom Error subclass for each variety of exception generated by the Box class. This approach gives our program the greatest ability to respond independently to different types of errors. But such flexibility is not necessarily required in many situations. Let the needs of your program's logic dictate how granular you make your errors.

Warning

In the first release of the Flash MX 2004 authoring tool, a bug prevents catch statements from executing when the catch parameter datatype refers to a class that has been imported via the import statement. For example, in the following code, the catch statement never executes because SomeCustomError is imported:

[as]import somePackage.SomeCustomError;
try {
throw new SomeCustomError( );
} catch(e:SomeCustomError) {
// This code should execute but it does not
// if the movie is compiled with version 7.0
// of the Flash MX 2004 authoring tool.
trace("Caught: " + e);
}[/as]

To work around the problem, we can include the full package name when specifying a custom error class as a catch block parameter. For example:

[as]import somePackage.SomeCustomError;
try {
throw new SomeCustomError( );
} catch(e:somePackage.SomeCustomError) {
// Now that the package name is included, this code runs.
trace("Caught: " + e);
}[/as]

This bug is fixed in the Flash MX 2004 updater, available at: http://macromedia.com/support/flash/downloads.html.


Exception Bubbling

Copyright © 2004 O'Reilly Media, Inc. All Rights Reserved.
Essential ActionScript 2.0
By Colin Moock
June 2004
ISBN: 0-596-00652-7
More info... .
Available from booksellers or direct from O'Reilly Media, www.oreilly.com.

Cover image
This content is excerpted from the above-named O'Reilly publication, with permission, by agreement with ActionScript.org.

Earlier we learned that exceptions in a try block can be thrown either directly or as the result of a method call. In reality, an exception can be thrown anywhere in an ActionScript program, even on a frame in a timeline! Given that an exception can be thrown anywhere, how does the ActionScript interpreter find the corresponding catch block to handle it? And what if no catch block exists? These mysteries are resolved through the magic of exception bubbling. Let's follow along a bubbly ride with the ActionScript interpreter as it encounters a throw statement in a program. During the following dramatization, the interpreter's musing are shown in code comments.

When a throw statement executes, the interpreter immediately stops normal program flow and looks for an enclosing try block. For example, here's a throw statement:

[as]// INTERPRETER: Hmm. A throw statement.
// Is there an enclosing try block for it?
throw new Error("Something went wrong");[/as]

If the throw statement is enclosed in a try block, the interpreter next tries to find a catch block whose parameter's datatype matches the datatype of the value thrown (in the present case, Error):

[as]// INTERPRETER: Great, I found a try block. Is there a matching catch block?
try {
throw new Error("Something went wrong");
}[/as]

If a matching catch block is found, the interpreter transfers program control to that block:

[as]try {
throw new Error("Something went wrong");
// INTERPRETER: Found a catch block whose parameter datatype is Error!
// The hunt's over. I'll execute this catch block now...
} catch (e:Error) {
// Handle problems...
}[/as]

But if a matching catch block cannot be found or if the throw statement did not appear within a try block in the first place, then the interpreter checks whether the throw statement occurred within a method or function. If the throw statement occurred in a method or function, the interpreter searches for a try block around the code that invoked the method or function. The following code demonstrates how the interpreter reacts when, within a method, it encounters a throw statement that has no enclosing try block:

[as]public function doSomething ( ):Void {
// INTERPRETER: Hmm. No try block here. I'll check who called this method.
throw new Error("Something went wrong");
}[/as]

If the code that invoked the method or function is enclosed in a try block, the interpreter looks for a matching catch block there and, if it finds a match, executes it. The following code demonstrates an exception thrown out of a method and caught where the method is invoked (i.e., one level up the call stack).

[as]class ErrorDemo {
public function doSomething ( ):Void {
// INTERPRETER: Hmm. No try block here.
// I'll check who called this method.
trace("About to throw an exception from doSomething( ) method.");
throw new Error("Something went wrong");
}

public static function startApp ( ):Void {
// INTERPRETER: Aha, here's who called doSomething( ). And here's
// an enclosing try block with a catch block whose
// parameter datatype is Error! My work's done. catch
// block, please execute now...
try {
var demo:ErrorDemo = new ErrorDemo( );
demo.doSomething( );
} catch (e:Error) {
// Handle problems...
trace("Exception caught in startApp( ), thrown by doSomething( ).");
}
}
}[/as]

Tip

The call stack is the list of functions and methods currently being executed by the interpreter at any given point in a program. The list includes the functions and methods in the reverse order from which they were called, from top to bottom. When a function is immediately below another function in the call stack, then the lower function was invoked by the higher. The lowest function in the call stack is the function currently executing.

You can use the Flash debugger to view the call stack for the current movie, as described in the Flash authoring tool online Help, under ActionScript Reference Guide → Debugging. Note, however, that the Flash debugger displays the currently executing function visually on top rather than on the bottom. That is, the visual display of the call stack in the Flash debugger is the reverse of the call stack discussed in the preceding note. Ultimately, the distinction is arbitrary—the glass is half full or it's half empty; the stack works top down or bottom up, depending on your point of view.

In the preceding code, an exception thrown by a method was caught by a try/catch block enclosing the method call statement. However, if no try block is found around the function or method caller, the interpreter searches up the entire call stack for a try block with a matching catch block. The following code shows a method throwing an error that is caught two levels up the call stack:

[as]class ErrorDemo {
public function doSomething ( ):Void {
// INTERPRETER: Hmm. No try block here.
// I'll check who called this method.
trace("About to throw an exception from doSomething( ) method.");
throw new Error("Something went wrong");
}

public static function startApp ( ):Void {
// INTERPRETER: Aha, here's who called doSomething( ). But still
// no try block here. I'll check who called this method.
var demo:ErrorDemo = new ErrorDemo( );
demo.doSomething( );
}
}

// Meanwhile, elsewhere in the program...
// INTERPRETER: Aha! Found a try block that has a catch block whose
// parameter's datatype is Error! My work's done. catch block,
// please execute now...
try {
ErrorDemo.startApp( );
} catch (e:Error) {
// Handle problems...
trace("Exception caught where Error.startApp( ) was invoked.");
}[/as]

Notice that the interpreter finds the try/catch block despite the fact that it surrounds not the error-throwing method, nor the caller of the error-throwing method, but the caller of the method that called the error-throwing method!


Uncaught Exceptions

Copyright © 2004 O'Reilly Media, Inc. All Rights Reserved.
Essential ActionScript 2.0
By Colin Moock
June 2004
ISBN: 0-596-00652-7
More info... .
Available from booksellers or direct from O'Reilly Media, www.oreilly.com.

Cover image
This content is excerpted from the above-named O'Reilly publication, with permission, by agreement with ActionScript.org.

We've seen a number of scenarios in which we've caught (trapped) various errors. But what happens if the interpreter never finds a catch block that can handle the thrown exception? If no eligible catch block is found anywhere in the call stack, then the interpreter:

  • Sends the value of the thrown Error object's message property to the Output panel (or simply sends the thrown expression itself if it wasn't an Error object)

  • Aborts execution of all code currently remaining in the call stack (including frame scripts and callback functions such as a setInterval( ) callback)

Execution of the program then resumes normally. For example, if an uncaught exception occurs on frame 1, layer 1 of a movie, the code on frame 2 executes normally. In fact, code on frame 1, layer 2 will also execute normally. For frame-based code, only the specific frame script that generated the uncaught exception is aborted.

The following code demonstrates a method that throws an error that is never caught:

[as]class ErrorDemo {
  public function doSomething ( ):Void {
    // INTERPRETER: Hmm. No try block here.
    // I'll check who called this method.
    throw new Error("Something went wrong");
  }

  public static function startApp ( ):Void {
    // INTERPRETER: Aha, here's who called doSomething( ). But still
    // no try block here. I'll check who called this method.
    doSomething( );
  }
}

// Meanwhile, elsewhere in the program...
// INTERPRETER: Hmm. Well, I searched all the way to the top, and still
//              no try block. I'll send "Something went wrong" to the Output
//              panel. Maybe the programmer will know what to do.
ErrorDemo.startApp( );[/as]

As we've just seen, because exceptions bubble up the call stack, it's not necessary for a method to catch its own exceptions. And it's not even necessary for the caller of a method to catch its exceptions. The exception can legally be caught at any level in the call stack. Any method can delegate exception handling to the code that calls it. That said, it's bad form and harmful to a program to throw an exception and then never catch it. You should always catch exceptions or, having encountered an uncaught exception, revise your code so that the exception isn't thrown in the first place.

Unfortunately, there's no way in ActionScript to tell the compiler to force a program to catch its own exceptions. As we'll learn later, ActionScript does not support Java-style checked exceptions, which are exceptions that must be caught in order for a program to compile. In Java terminology, all exceptions in ActionScript are unchecked.


The finally Block

Copyright © 2004 O'Reilly Media, Inc. All Rights Reserved.
Essential ActionScript 2.0
By Colin Moock
June 2004
ISBN: 0-596-00652-7
More info... .
Available from booksellers or direct from O'Reilly Media, www.oreilly.com.

Cover image
This content is excerpted from the above-named O'Reilly publication, with permission, by agreement with ActionScript.org.

So far, we've discussed only the try and catch blocks in the try/catch/finally statement. As we've seen, a try block contains code that might throw an exception, and a catch block executes code in response to a thrown exception. The finally block, by comparison, always executes, whether or not code in the try block throws an exception.

The finally block is placed once (and only once) as the last block in a try/catch/finally statement. For example:

[as]try {
  // Statements
} catch (e:ErrorType1) {
  // Handle ErrorType1 exceptions.
} catch (e:ErrorTypen) {
  // Handle ErrorTypen exceptions.
} finally {
  // This code always executes, no matter how the try block exits.
}[/as]

Misplacing the finally block causes a compile-time error.

In the preceding code, the finally block executes in one of the four circumstances:

  • Immediately after the try block completes without errors

  • Immediately after a catch block handles an exception generated in the try block

  • Immediately before an uncaught exception bubbles up

  • Immediately before a return, continue, or break statement transfers control out of the try or catch blocks (but see the bug described under "A Nested Exception Bug," later in this chapter)

The finally block of a try/catch/finally statement typically contains cleanup code that must execute whether or not an exception occurs in the corresponding try block. For example, suppose we're creating a space shooter game and we define a class, SpaceShip, to represent spaceships in the game. The SpaceShip class has a method, attackEnemy( ) that performs the following tasks:

  • Sets the spaceship's current target

  • Fires on that target

  • Clears the current target (i.e., sets the SpaceShip's currentTarget property to null)

In our hypothetical application, we'll assume that the first two of the preceding tasks might generate an exception. Further, we'll assume that the attackEnemy( ) method doesn't handle those exceptions itself; instead, it passes them up to the calling method. But whether or not an exception is generated, the attackEnemy( ) method must set the currentTarget property to null.

Here's what the attackEnemy( ) method would look like if we coded it with a catch statement (i.e., without using finally):

[as]public function attackEnemy (enemy:SpaceShip):Void {
  try {
    setCurrentTarget(enemy);
    fireOnCurrentTarget( );
  } catch (e:Error) {
    // Clear the current target if an exception occurs.
    setCurrentTarget(null);
    // Pass the exception up to the calling method.
    throw e;                 
  }
  // Clear the current target if no exception occurs.
  setCurrentTarget(null);    
}[/as]

Notice that we must duplicate the statement, setCurrentTarget(null). We place it both in the catch block and after the try/catch statement, guaranteeing that it will run whether or not there's an exception in the try block. But duplicating the statement is error prone. In the preceding method, a programmer could have easily forgotten to clear the current target after the try/catch block.

If we change our strategy by clearing the current target in a finally block, we remove the redundancy in the preceding code:

[as]public function attackEnemy (enemy:SpaceShip):Void {
  try {
    setCurrentTarget(enemy);
    fireOnCurrentTarget( );
  } finally {
    setCurrentTarget(null);
  }
}[/as]

In the revised version, the finally block clears the current target whether there's an exception or not. Because both situations are handled, we no longer have any need for a catch block; we can simply let the exception bubble up to the calling method automatically.

You might be wondering why we need the finally block at all. That is, why not just use the following code?

[as]// This code might look decent, but there's a problem. Can you spot it?
public function attackEnemy (enemy:SpaceShip):Void {
  setCurrentTarget(enemy);
  fireOnCurrentTarget( );
  setCurrentTarget(null);
}[/as]

Remember that when an exception is thrown, program control is transferred to the nearest suitable catch block in the call stack. Hence, if fireOnCurrentTarget( ) throws an exception, control transfers out of attackEnemy( ), never to return, and setCurrentTarget(null) would never execute. By using a finally block, we guarantee that setCurrentTarget(null) executes before the exception bubbles up.

The attackEnemy( ) method example reflects the most common use of finally in multithreaded languages like Java, where a program can have multiple sections of code executing simultaneously. In Java, the following general structure is commonplace—it guards against the possibility that an object busy with a task might be inappropriately altered by another object during the execution of that task:

[as]// Set a state indicating this object's current task.
// External objects should check this object's state
// before accessing or manipulating this object.
doingSomething = true;     
try {
  // Perform the task.
  doSomething( );           
} finally {
  // Unset the "in-task" state (whether or not
  // the task generated an exception).
  doingSomething = false;  
}[/as]

In ActionScript, the preceding state-management code is effectively unnecessary because the language is single-threaded, so no external object will ever attempt to alter the state of an object while it is busy executing a method. Hence, finally is used much more rarely in ActionScript than it is in multithreaded languages. However, it can still be used for organizational purposes, to contain code that performs cleanup duties after other code has executed.


Nested Exceptions

Copyright © 2004 O'Reilly Media, Inc. All Rights Reserved.
Essential ActionScript 2.0
By Colin Moock
June 2004
ISBN: 0-596-00652-7
More info... .
Available from booksellers or direct from O'Reilly Media, www.oreilly.com.

Cover image
This content is excerpted from the above-named O'Reilly publication, with permission, by agreement with ActionScript.org.

So far we've used only single-level try/catch/finally statements, but exception-handling logic can also be nested. A try/catch/finally statement can appear inside the try, catch, or finally block of another try/catch/finally statement. This hierarchical nesting allows any block in a try/catch/finally statement to execute code that might, itself, throw an error.

For example, suppose we were writing a multiuser, web-based message board system. We define a core class for the application (called BulletinBoard), another class that manages the user interface (called GUIManager), and another class that represents a user on the board (called User). We give BulletinBoard a method, populateUserList( ), which displays the list of current active users. The populateUserList( ) method splits its work into two stages: first it retrieves a List component from the application's GUIManager instance, then it populates that List with users from a supplied array of User instances. These two stages can both potentially generate an exception, so a nested try/catch/finally structure is used in the populateUserList( ) method. Let's take a closer look at this nested structure.

During the first stage of populateUserList( ), if the List component isn't available, a UserListNotFound exception is thrown by the GUIManager. The UserListNotFound exception is caught by the outer try/catch/finally statement.

If, on the other hand, the List component is available, the populateUserList( ) method proceeds with stage two, during which a loop populates the List component with users from the supplied array. For each iteration through the loop, if the current user's ID cannot be found, the User.getID( ) method throws a UserIdNotSet exception. The UserIdNotSet exception is caught by the nested try/catch/finally statement.

Here's the code (we won't work closely with components until Chapter 12, so if some of this code looks new to you, try returning to it once you're finished reading that chapter):

[as]public function populateUserList (users:Array):Void {
  try {
    // Start stage 1...get the List.
    // If getUserList( ) throws an exception, the outer catch executes.
    var ulist:List = getGUIManager( ).getUserList( );
    // Start stage 2...populate the List.
    for (var i:Number = 0; i < users.length; i++) {
      try {
        var thisUser:User = User(users[i]);
        // If getID( ) throws an exception, the nested catch executes.
        ulist.addItem(thisUser.getName( ), thisUser.getID( ));
      } catch (e:UserIdNotSet) {
        trace(e);
        log.warn(e);
        continue;  // Skip this user.
      }
    }
  } catch (e:UserListNotFound) {
    trace(e);
    log.warn(e);
  }
}[/as]

Now that we've had a look at a specific nested exception example, let's consider how nested exceptions are handled in general.

If an exception occurs in a try block that is nested within another try block, and the inner try block has a catch block that can handle the exception, then the inner catch block is executed and the program resumes at the end of the inner try/catch/finally statement.

[as]try {
  try {
    // Exception occurs here.
    throw new Error("Test error");
  } catch (e:Error) {
    // Exception is handled here.
    trace(e);  // Displays: Test error
  }
  // The program resumes here.
} catch (e:Error) {
  // Handle exceptions generated by the outer try block.
}[/as]

If, on the other hand, an exception occurs in a try block that is nested within another try block, but the inner try block does not have a catch block that can handle the exception, then the exception bubbles up to the outer try/catch/finally statement (and, if necessary, up the call stack) until a suitable catch block is found or the exception is not caught. If the exception is caught somewhere in the call stack, the program resumes at the end of the try/catch/finally statement that handled the exception. Note that in the following code example (and subsequent examples), the hypothetical error datatype SomeSpecificError is a placeholder used to force the thrown exception to not be caught. In order to test the code example in your own code, you'd have to create a subclass of Error called SomeSpecificError.

[as]try {
  try {
    // Exception occurs here.
    throw new Error("Test error");
  } catch (e:SomeSpecificError) {
    // Exception is not handled here.
    trace(e);  // Never executes because the types don't match.
  }
} catch (e:Error) {
  // Exception is handled here.
  trace(e);  // Displays: Test error
}
// The program resumes here, immediately after the outer catch block
// has handled the exception.[/as]



Nested Exceptions Continued

Copyright © 2004 O'Reilly Media, Inc. All Rights Reserved.
Essential ActionScript 2.0
By Colin Moock
June 2004
ISBN: 0-596-00652-7
More info... .
Available from booksellers or direct from O'Reilly Media, www.oreilly.com.

Cover image
This content is excerpted from the above-named O'Reilly publication, with permission, by agreement with ActionScript.org.

If an exception occurs in a try block that is nested within a catch block, and the inner try block does not have a catch block that can handle the exception, then the search for a matching catch block begins outside the outer try/catch/finally statement:

[as]try {
  // Outer exception occurs here.
  throw new Error("Test error 1");
} catch (e:Error) {
  // Outer exception handled here.
  trace(e);  // Displays: Test error 1
  try {
    // Inner exception occurs here.
    throw new Error("Test error 2");
  } catch (e:SomeSpecificError) {
    // Inner exception is not handled here.
    trace(e);  // Never executes because the types don't match.
  }
}
// The search for a matching catch block for the inner exception starts here.[/as]

Last, if an exception occurs in a try block that is nested within a finally block, but a prior exception is already in the process of bubbling up the call stack, then the new exception is handled before the prior exception continues to bubble up.

[as]// This method throws an exception in a finally block.
public function throwTwoExceptions ( ):Void {
  try {
    // Outer exception occurs here. Because there is no catch block for this
    // try block, the outer exception starts to bubble up,
    // out of this method.
    throw new Error("Test error 1");
  } finally {
    try {
      // Inner exception occurs here. The inner exception is
      // handled before the outer exception actually bubbles up.
      throw new Error("Test error 2");
    } catch (e:Error) {
      // Inner exception is handled here.
      trace("Internal catch: " + e);
    }
  }
}

// Elsewhere, within another method that calls the preceding method.
try {
  throwTwoExceptions( );
} catch (e:Error) {
  // The outer exception, which has bubbled up from throwTwoExceptions( ),
  // is handled here.
  trace("External catch: " + e);
}[/as]

[code]// Output (notice that the inner exception is caught first):
Internal catch: Test error 2
External catch: Test error 1[/code]

If, in the preceding example, the exception thrown in the finally block had never been caught, then the interpreter would have sent it to the Output panel and aborted all other code in the call stack. As a result, the original, outer exception would have been discarded along with all code in the call stack. The following code demonstrates the preceding principle. It throws an uncaught exception from a finally statement. As a result, the exception thrown by the outer try block is discarded. To test the code, you can run it directly on a frame in the timeline.

[as]try {
  // Outer exception occurs here.
  throw new Error("Test error 1");
} finally {
  try {
    // Inner exception occurs here.
    throw new Error("Test error 2");
  } catch (e:SomeSpecificError) {
    // Inner exception is not handled here.
    trace("internal catch: " + e); // Never executes because
                                   // the types don't match.
  }
}
// The search for a matching catch block for the inner exception starts
// here. If no match is ever found, then the Output panel displays
// "Test error 2", and the bubbling of the outer exception is aborted.[/as]

The preceding code demonstrates the effect of an uncaught exception in one scenario, but once again, it's not appropriate to allow an exception to go uncaught. In the preceding case, we should either catch the exception or revise our code so that the exception isn't thrown in the first place.


A Nested Exception Bug

Copyright © 2004 O'Reilly Media, Inc. All Rights Reserved.
Essential ActionScript 2.0
By Colin Moock
June 2004
ISBN: 0-596-00652-7
More info... .
Available from booksellers or direct from O'Reilly Media, www.oreilly.com.

Cover image
This content is excerpted from the above-named O'Reilly publication, with permission, by agreement with ActionScript.org.

Earlier we learned that finally executes no matter what happens in a try block. In theory, that should be true. However, a bug in Flash Player 7 prevents finally from executing when a nested try/catch/finally statement returns from a function, as shown in the following code:

[as]function nestedFinallyBugDemo ( ):Void {
  try {
    try {
      throw new Error("Test error");
    } finally {
      trace("Inner finally block.");
      return;
    }
  } finally {
    trace("Outer finally block.");  // Due to a bug in Flash Player 7,
                                    // this line never executes.
  }
}
nestedFinallyBugDemo( );[/as]

[code]// Displays:
Inner finally block.[/code]

The need for this structure is rare, so you probably won't encounter the bug, and Macromedia may very well fix it in Flash Player 8 if not an interim version of Flash Player 7.

Control Flow Changes in try/catch/finally

As we've seen throughout this chapter, the throw statement changes the flow of a program. When the interpreter encounters a throw statement, it immediately stops what it's doing and transfers program control to eligible catch and finally blocks. However, it is also quite legal for those catch and finally blocks to change program flow again via return (in the case of a method or function) or break or continue (in the case of a loop). Furthermore, a return, break , or continue statement can also appear in a try block.

To learn the rules of flow changes in the try/catch/finally statement, let's look at how the return statement affects program flow in a try, catch, and finally block. The following code examples contain a method, changeFlow( ) that demonstrates a control flow in various hypothetical situations. Note that, in all cases, the behavior of the changeFlow( ) method would be the same if it were a standalone function.

Example 10-1 shows a return statement in a try block, placed before an error is thrown. In this case, the method returns normally, and no error is ever thrown or handled. However, before the method returns, the finally block is executed. Note that you're unlikely to see code exactly like Example 10-1 in the real world. In most applied cases, the return statement would occur in a conditional statement and execute in response to some specific condition in the program.

Example 10-1. Using return in try, before throw

[as]function changeFlow ( ):Void {
  try {
    return;
    throw new Error("Test error.");
  } catch (e:Error) {
    trace("Caught: " + e);
  } finally {
    trace("Finally executed.");
  }
  trace("Last line of method.");
}[/as]

[code]// Output when changeFlow( ) is invoked:
Finally executed.[/code]

Example 10-2 shows a return statement in a try block, placed after an error is thrown. In this case, the return statement is never executed because an error is thrown before it is reached. Once the error is caught and the try/catch/finally completes, execution resumes after the try/catch/finally statement, and the method exits at the end of the method body. Again, Example 10-2 demonstrates a principle but is atypical of real-world code, which would normally throw the error based on some condition.


More examples

Copyright © 2004 O'Reilly Media, Inc. All Rights Reserved.
Essential ActionScript 2.0
By Colin Moock
June 2004
ISBN: 0-596-00652-7
More info... .
Available from booksellers or direct from O'Reilly Media, www.oreilly.com.

Cover image
This content is excerpted from the above-named O'Reilly publication, with permission, by agreement with ActionScript.org.

Example 10-2. Using return in try, after throw

[as]function changeFlow ( ):Void {
  try {
    throw new Error("Test error.");
    return;
  } catch (e:Error) {
    trace("Caught: " + e);
  } finally {
    trace("Finally executed.");
  }
  trace("Last line of method.");
}[/as]

[code]// Output when changeFlow( ) is invoked:
Caught: Test error.
Finally executed.
Last line of method.[/code]

Example 10-3 shows a return statement in a catch block. In this case, the return statement executes when the work of error handling is done, and the code after the try/catch/finally statement never executes. However, as usual, before the method returns, the finally block is executed. Unlike Examples 10-1 and Example 10-2, this code is typical of a real-world scenario in which a method is aborted due to the occurrence of an error.

Example 10-3. Using return in catch

[as]function changeFlow ( ):Void {
  try {
    throw new Error("Test error.");
  } catch (e:Error) {
    trace("Caught: " + e);
    return;
  } finally {
    trace("Finally executed.");
  }
  trace("Last line of method.");
}[/as]

[code]// Output when changeFlow( ) is invoked:
Caught: Test error.
Finally executed.[/code]

Last, Example 10-4 shows a return statement in a finally block. In this case, the return statement executes when the finally block executes (as we learned earlier, a finally block executes when its corresponding try block completes in one of the following ways: without errors, with an error that was caught, with an error that was not caught, or due to a return, break, or continue statement). Notice that the return statement in Example 10-4 prevents any code in the method beyond the try/catch/finally statement from executing. You might use a similar technique to quit out of a method after invoking a block of code, whether or not that code throws an exception. In such a case, you'd typically surround the entire try/catch/finally block in a conditional statement (otherwise the remainder of the method would never execute!).

Example 10-4. Using return in finally

[as]function changeFlow ( ):Void {
  try {
    throw new Error("Test error.");
  } catch (e:Error) {
    trace("Caught: " + e);
  } finally {
    trace("Finally executed.");
    return;
  }
  trace("Last line of method.");  // Not executed.
}[/as]

[code]// Output when changeFlow( ) is invoked:
Caught: Test error.
Finally executed.[/code]

If a return statement occurs in a finally block after a return has already been issued in the corresponding try block, then the return in the finally block replaces the return already in progress.


Limitations of Exception Handling in ActionScript 2.0

Copyright © 2004 O'Reilly Media, Inc. All Rights Reserved.
Essential ActionScript 2.0
By Colin Moock
June 2004
ISBN: 0-596-00652-7
More info... .
Available from booksellers or direct from O'Reilly Media, www.oreilly.com.

Cover image
This content is excerpted from the above-named O'Reilly publication, with permission, by agreement with ActionScript.org.

As you use exceptions in your code, you should be aware of the following exception-handling limitations in ActionScript 2.0.

No Checked Exceptions

In ActionScript 2.0, if a method throws an exception, it's up to the programmer to know about it in advance and handle it appropriately. Nothing you write in your code can force you or anyone else to handle an exception. The compiler will make no complaints if an exception is not handled. (Of course, an uncaught exception can cause code to be aborted at runtime, as discussed earlier under Section 10.3.1)

Exceptions are, hence, harder to work with in ActionScript than in languages that support compile-time errors for uncaught exceptions. In Java, for example, an exception can be either checked (a compile-time error occurs if the exception is not handled) or unchecked (no compile-time error occurs if the exception is not handled).

In Java, the declaration for a method that throws a checked exception explicitly includes the exception in the method header. Here's a Java method declaration that explicitly states the type of exception the method can throw (in this case the method can throw exceptions of type IOException):

[as]public void someMethod( ) throws IOException {
}[/as]

Upon reading the preceding method declaration, the programmer immediately knows she must handle IOExceptions when invoking the method. If the programmer invokes the method without catching or rethrowing the exception, Java will produce an error and the program will not compile. This strictness forces programmers to dutifully deal with exceptions, leading to less time debugging exception-related errors. ActionScript doesn't support the throws clause in method declarations.

No Built-in Exceptions

We saw earlier that Flash doesn't throw an exception when a developer attempts an illegal operation, such as dividing by zero. Neither the class library built into the Flash Player nor the Flash MX 2004 v2 components throw exceptions. The only exceptions generated in Flash are those thrown by custom-written programs. This exception-less environment leads to two problems. First, Flash must relay all error information to the programmer using unwieldy error codes or return values. Second (and much worse), Flash often fails silently when a runtime error occurs. Silent errors are very difficult to track down. In the future, as versions of the Flash Player that support exception handling are adopted, it is likely that ActionScript will generate more built-in exceptions.

Exception Performance Issues

In programming languages that do not support exceptions, programmers often use error codes to represent error conditions. That is, if a method or function encounters an error, it returns a message or code describing the problem and expects the caller to know how to interpret that code. While more awkward than exceptions in many situations, this older, grassroots style of signaling an error in a program is actually faster than throwing an exception.

For this reason, even with exceptions supported in ActionScript, returned error codes are sometimes still a good technique for error handling, primarily in situations that are highly performance intensive. Both exception handling and error codes are useful and can be used together in different parts of the same program, depending upon the performance needs of different parts of your code.

But before you get excited about speed improvements and swear off exceptions for life, be aware that in most programs, no human-perceivable improvement in speed results from using error codes instead of exceptions. But in situations in which every last bit of performance makes a difference to the program, it may be necessary to forego exceptions in favor of faster error codes.

The speed test in Example 10-5 compares the amount of time it takes to throw and catch 1000 exceptions with the amount of time it takes to handle a return value of false 1000 times. On average, throwing an exception takes approximately three times as long as handling the return value. However, on my aging Pentium III 700 MHz Windows test machine, the entire process of throwing 1000 exceptions took a mere 187 milliseconds (compared to 57 milliseconds to handle the false return values). In most programs, this potential 130-millisecond savings will be irrelevant. In the vast majority of cases, the benefits of clean exception-based code far outweigh the performance costs.

Example 10-5. Exception handling benchmark test

[as]// The class file, in ExceptionPerformanceTest.as.
class ExceptionPerformanceTest {
  public function test1 ( ):Void {
    throw new CustomException( );
  }

  public function test2 ( ):Boolean {
    return false;
  }
}

// The exception class in CustomException.as.
class CustomException extends Error {
  public var message:String = "This is the error message.";
}

// The test code, in ExceptionPerformanceTest.fla.
var ept:ExceptionPerformanceTest = new ExceptionPerformanceTest( );

var count1:Number = 0;
var count2:Number = 0;

var start1:Number = getTimer( );
for (var i:Number = 0; i < 1000; i++) {
  try {
    ept.test1( );
  } catch (e:Error) {
    count1++;
  }
}
var elapsed1 = getTimer( ) - start1;

var start2:Number = getTimer( );
for (var i:Number = 0; i < 1000; i++) {
  if (!ept.test2( )) {
    count2++;
  }
}
var elapsed2 = getTimer( ) - start2;

trace(elapsed1);  // On my Pentium III 700, displays: 187
trace(elapsed2);  // On my Pentium III 700, displays: 57[/as]

From Concepts to Code

We've come to the end of Part I and finished our study of ActionScript 2.0 OOP theory. If you've read and understood everything up to this point (or at least most of it), you now have the conceptual foundation required to create object-oriented Flash applications. From here on out, you simply need to build practical experience on top of that foundation. Parts II and III of this book will help you do just that. In Part II, we'll study application frameworks, visual programming, and code distribution. In Part III, we'll explore several widely accepted solutions to specific architectural problems in object-oriented programming.