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):

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);
  }
}

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.

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.
}

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.

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.