Moris' Note Book

本博客所有内容皆收集于网上,非本人原创,非心情日记,非研究心得,只是自己浏览的东西的收集
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

Understanding Try/Catch in ActionScript

Posted on 2008-01-22 13:12  moris  阅读(443)  评论(0)    收藏  举报

By: Joey Lott

Lot's of people have been asking about how and when to use try/catch functionality in the Flash applications. This article aims to introduce you to the basic concepts of working with try/catch. In this article you'll learn:

  • What try/catch does
  • How and where to implement basic try/catch functionality
  • How to build your own custom error classes
  • How to implement advanced try/catch functionality

Understanding try/catch

The idea with try/catch is that some portion of your code may possibly fail to work, and you want to be able to catch it if it does. It's important to understand what types of errors try/catch can handle, however. It cannot handle syntax errors (that's the compiler's task.) Nor can it handle errors in asynchronous scenarios. For example, you cannot use try/catch methodology to handle an error that may occur should a an XML document fail to parse properly. You can, however, use try/catch to handle errors that are introduced because of invalid parameters, etc.

So then, what are some scenarios in which you could utilize try/catch? Consider the following scenario: You have a function that draws a rectangle such as:

function makeRectangle(nWidth:Number, nHeight:Number):MovieClip {

  // Determine the next depth within the current
  // timeline.
  var nDepth:Number = this.getNextHighestDepth();

  // Create a new movie clip.
  var mcClip:MovieClip = this.createEmptyMovieClip("mcClip" + nDepth, nDepth);

  // Set the linestyle, then draw a filled
  // rectangle with the specified dimensions.
  mcClip.lineStyle(0, 0, 100);
  mcClip.beginFill(0xFFFFFF, 100);
  mcClip.lineTo(nWidth, 0);
  mcClip.lineTo(nWidth, nHeight);
  mcClip.lineTo(0, nHeight);
  mcClip.lineTo(0, 0);
  mcClip.endFill();

  // Return a reference to the movie clip.
  return mcClip;
}

The function works just fine as it stands. However, notice that it doesn't take into account any possible errors that could be introduced because of invalid values for the parameters. For example, if you try to pass in an undefined value, then the function will go on it's merry way thinking that it did what it was asked to do. But obviously, given an undefined value for one or both of the parameters, the function will not be able to create a valid rectangle. One of the options that ActionScript programmers have employed previously was to return different values depending on whether or not an error was encountered. For example, the preceding function could be modified as follows:

function makeRectangle(nWidth:Number, nHeight:Number):MovieClip {

  // Check to see if the width and/or height is
  // undefined. If so, return a value of null.
  if(nWidth == undefined || nHeight == undefined) {
    return null;
  }
  var nDepth:Number = this.getNextHighestDepth();
  var mcClip:MovieClip = this.createEmptyMovieClip("mcClip" + nDepth, nDepth);
  mcClip.lineStyle(0, 0, 100);
  mcClip.beginFill(0xFFFFFF, 100);
  mcClip.lineTo(nWidth, 0);
  mcClip.lineTo(nWidth, nHeight);
  mcClip.lineTo(0, nHeight);
  mcClip.lineTo(0, 0);
  mcClip.endFill();
  return mcClip;
}

Then, you can use the function with an if statement. For example:

function run(nWidth1:Number, nHeight1:Number):Void {

  // Try to create the rectangle.
  var mcRectangle1:MovieClip = makeRectangle(nWidth1, nHeight1);

  // Check to see if the rectangle was created
  // successfully. If not, then display an error
  // message.
  if(mcRectangle1 == null) {
    tOutput.text = "One of the specified dimensions is undefined.";
  }
}

While that system works, it can get fairly complicated quite quickly. For example, you may want to create four rectangles, but if any one of the rectangles fails, you want to remove the previous rectangles and exit the subroutine. Then your code would look something more like the following:

function run(nWidth1:Number, nHeight1:Number, nWidth2:Number, nHeight2:Number, nWidth3:Number, nHeight3:Number, nWidth4:Number, nHeight4:Number):Void {

  // Try to create the first rectangle.
  var mcRectangle1:MovieClip = makeRectangle(nWidth1, nHeight1);

  // Check to see if the rectangle was created
  // successfully. If not, then display an error
  // message and exit the function.
  if(mcRectangle1 == null) {
    tOutput.text = "One of the specified dimensions is undefined.";
    return;
  }

  // Try to create the second rectangle.
  var mcRectangle2:MovieClip = makeRectangle(nWidth2, nHeight2);

  // Check to see if the rectangle was created
  // successfully. If not, then display an error
  // message, remove the previous rectangle, and
  // exit the function.
  if(mcRectangle2 == null) {
    tOutput.text = "One of the specified dimensions is undefined.";
    mcRectangle1.removeMovieClip();
    return;
  }

  // Try to create the third rectangle.
  var mcRectangle3:MovieClip = makeRectangle(nWidth3, nHeight3);

  // Check to see if the rectangle was created
  // successfully. If not, then display an error
  // message, remove the previous rectangles, and
  // exit the function.
  if(mcRectangle3 == null) {
    tOutput.text = "One of the specified dimensions is undefined.";
    mcRectangle1.removeMovieClip();
    mcRectangle2.removeMovieClip();
    return;
  }

  // Try to create the fourth rectangle.
  var mcRectangle4:MovieClip = makeRectangle(nWidth4, nHeight4);

  // Check to see if the rectangle was created
  // successfully. If not, then display an error
  // message, remove the previous rectangles, and
  // exit the function.
  if(mcRectangle4 == null) {
    tOutput.text = "One of the specified dimensions is undefined.";
    mcRectangle1.removeMovieClip();
    mcRectangle2.removeMovieClip();
    mcRectangle3.removeMovieClip();
    return;
  }

}

 

Note that at most only three movie clips need to be removed. That is because at most three movie clips are created in the event that any one of the calls to makeRectangle() causes an error. That holds for the rest of the examples in this article. In none of the examples will it be necessary to remove more than three movie clips.

As you can see, it gets rather cluttered and difficult to read as you add more rectangles. Using try/catch, however, simplifies the code, and makes it much clearer to read. The makeRectangle() function can be rewritten as follows:

function makeRectangle(nWidth:Number, nHeight:Number):MovieClip {

  // Check to see if the width and/or height is
  // undefined. If so, throw an error.
  if(nWidth == undefined || nHeight == undefined) {
    throw new Error("Undefined dimension");
  }
  var nDepth:Number = this.getNextHighestDepth();
  var mcClip:MovieClip = this.createEmptyMovieClip("mcClip" + nDepth, nDepth);
  mcClip.lineStyle(0, 0, 100);
  mcClip.beginFill(0xFFFFFF, 100);
  mcClip.lineTo(nWidth, 0);
  mcClip.lineTo(nWidth, nHeight);
  mcClip.lineTo(0, nHeight);
  mcClip.lineTo(0, 0);
  mcClip.endFill();
  return mcClip;
}

Then, you can simplify the run() function as follows:

function run(nWidth1:Number, nHeight1:Number, nWidth2:Number, nHeight2:Number, nWidth3:Number, nHeight3:Number, nWidth4:Number, nHeight4:Number):Void {

  var mcRectangle1:MovieClip;
  var mcRectangle2:MovieClip;
  var mcRectangle3:MovieClip;
  var mcRectangle4:MovieClip;

  try {
    mcRectangle1 = makeRectangle(nWidth1, nHeight1);
    mcRectangle2 = makeRectangle(nWidth2, nHeight2);
    mcRectangle3 = makeRectangle(nWidth3, nHeight3);
    mcRectangle4 = makeRectangle(nWidth4, nHeight4);
  }
  catch (errObject:Error) {
    mcRectangle1.removeMovieClip();
    mcRectangle2.removeMovieClip();
    mcRectangle3.removeMovieClip();
    tOutput.text = "An error occurred: " + errObject.message;
  }
}

Even though you may not yet understand the throw statement or the try/catch block, you can likely already see that the code is much more readable using try/catch than what it was using the previous if statement workaround. So now that you've had a chance to see generally what try/catch can do, let's next take a look at the syntax and how it works.

Using try/catch

The first thing you'll need to understand is the throw statement. Whenever Flash encounters a throw statement, it immediately stops the current process, and it looks to find some code that can catch the value that is thrown. What that means is that no more code within a routine will run if Flash encounters a throw statement. Though a throw statement is not meant to be a substitute for a return statement, it does share some functionality in common with a return statement in that as soon as either is run within a function or method, the function or method stops. However, a throw statement not only exits a function, but it can also stop routines running on frames.

A throw statement operates on a value. Typically that value is an Error object, though it could also be any other data type. In the makeRectangle() function example you saw that a new Error object was thrown using the following line:

throw new Error("Undefined dimension");

The Error class has a message property that you can set by passing a value into the constructor. Then, when the error is caught, you can retrieve the message value if applicable.

Once the error is thrown, Flash next attempts to find the code that can catch the error to handle it. In order to be able to catch an error, you need to have a try/catch block in place, and the throw statement needs to be run from within the try block. You can even run a throw statement directly within the try block as in the following example:

try {
  throw new Error("Some error");
}
catch(errObject:Error) {
  trace(errObject.message);
}

Of course the preceding code would be rather pointless in and of itself since it would throw the error every time. However, it does illustrate the simplest form of a try/catch block.

More frequently the throw statement occurs within a function or method, and then the function or method is called within the try block. The example with the makeRectangle() function is an example of that. Another, extremely simple example of that is as follows:

function someFunction():Void {
  throw new Error("Some error");
}

try {
  someFunction();
}
catch(errObject:Error) {
  trace(errObject.message);
}

Again, the code is pointless in and of itself. But it does illustrate the point. The throw statement occurs within someFunction(). When that error is thrown, Flash first looks within someFunction() to see if the throw statement occurs directly within a try/catch block. Since it does not, Flash next looks to the next location on the call stack - the routine from within which the function was called. It looks to see if the function was called within a try/catch block. In the preceding example, that is the case. So Flash would then send the thrown error to the catch block of the corresponding try statement.

You can see that a catch block is passed a parameter. The parameter that is passed to the catch block is the value or reference that was thrown and subsequently caught. Therefore, in the preceding example the catch statement is passed a reference to the Error object that was thrown from someFunction().

So having discussed the basics of the syntax and functionality of throwing and catching errors, let's next review the makeRectangle() and run() functions to see how they work.

The makeRectangle() function is fairly simple. It merely checks to see if either of the parameters are undefined. If so, it uses a throw statement to throw a new Error message. If that occurs, then the rest of the function is not run. Otherwise, the function runs normally, and it draws a rectangle.

function makeRectangle(nWidth:Number, nHeight:Number):MovieClip {

  // Check to see if the width and/or height is
  // undefined. If so, throw an error.
  if(nWidth == undefined || nHeight == undefined) {
    throw new Error("Undefined dimension");
  }
  var nDepth:Number = this.getNextHighestDepth();
  var mcClip:MovieClip = this.createEmptyMovieClip("mcClip" + nDepth, nDepth);
  mcClip.lineStyle(0, 0, 100);
  mcClip.beginFill(0xFFFFFF, 100);
  mcClip.lineTo(nWidth, 0);
  mcClip.lineTo(nWidth, nHeight);
  mcClip.lineTo(0, nHeight);
  mcClip.lineTo(0, 0);
  mcClip.endFill();
  return mcClip;
}

The run() function below uses a try block to surround any code that may potentially throw errors - the calls to makeRectangle(). If the calls to makeRectangle() run without throwing any errors, then the four rectangles are created. However, if any one of the rectangles should thrown an error, the try block stops running, and the catch block handles the error by removing any previously-created rectangles and displaying an error message.

function run(nWidth1:Number, nHeight1:Number, nWidth2:Number, nHeight2:Number, nWidth3:Number, nHeight3:Number, nWidth4:Number, nHeight4:Number):Void {

  var mcRectangle1:MovieClip;
  var mcRectangle2:MovieClip;
  var mcRectangle3:MovieClip;
  var mcRectangle4:MovieClip;

  try {
    mcRectangle1 = makeRectangle(nWidth1, nHeight1);
    mcRectangle2 = makeRectangle(nWidth2, nHeight2);
    mcRectangle3 = makeRectangle(nWidth3, nHeight3);
    mcRectangle4 = makeRectangle(nWidth4, nHeight4);
  }
  catch (errObject:Error) {
    mcRectangle1.removeMovieClip();
    mcRectangle2.removeMovieClip();
    mcRectangle3.removeMovieClip();
    tOutput.text = "An error occurred: " + errObject.message;
  }
}

Handling Specific Errors

In the examples you've seen up to this point, there has only been one type of error. However, there are benefits to throwing and handling different types of errors depending on what occurs. For example, you may want to throw one type of error if the dimensions are undefined, but another type of error if the dimensions are defined but out of range. In order to throw different types of errors, you should create subclasses of Error. For example, you could create the following classes:

class UndefinedError extends Error {

  function UndefinedError() {
    super("Undefined value");
  }

}

class OutOfRangeError extends Error {

  function OutOfRangeError() {
    super("Value out of range");
  }

}

Then, you can throw different errors based on different occurrences. For example, the makeRectangle() function can be rewritten as follows:

function makeRectangle(nWidth:Number, nHeight:Number):MovieClip {

  if(nWidth == undefined || nHeight == undefined) {
    throw new UndefinedError();
  }
  else if(nWidth <= 0 || nHeight <= 0) {
    throw new OutOfRangeError();
  }

  var nDepth:Number = this.getNextHighestDepth();
  var mcClip:MovieClip = this.createEmptyMovieClip("mcClip" + nDepth, nDepth);
  mcClip.lineStyle(0, 0, 100);
  mcClip.beginFill(0xFFFFFF, 100);
  mcClip.lineTo(nWidth, 0);
  mcClip.lineTo(nWidth, nHeight);
  mcClip.lineTo(0, nHeight);
  mcClip.lineTo(0, 0);
  mcClip.endFill();
  return mcClip;
}

If different errors can be thrown, then you can set up different catch blocks to handle the different types of errors. A catch block is only run if it is set to catch the type of error that has been thrown. Therefore, you can have a sequence of catch blocks that expect different types of errors, and only one will get called. Given the rewrite of makeRectangle(), you can rewrite run() as follows:

function run(nWidth1:Number, nHeight1:Number, nWidth2:Number, nHeight2:Number, nWidth3:Number, nHeight3:Number, nWidth4:Number, nHeight4:Number):Void {

  var mcRectangle1:MovieClip;
  var mcRectangle2:MovieClip;
  var mcRectangle3:MovieClip;
  var mcRectangle4:MovieClip;

  try {
    mcRectangle1 = makeRectangle(nWidth1, nHeight1);
    mcRectangle2 = makeRectangle(nWidth2, nHeight2);
    mcRectangle3 = makeRectangle(nWidth3, nHeight3);
    mcRectangle4 = makeRectangle(nWidth4, nHeight4);
  }
  catch (udObject:UndefinedError) {
    mcRectangle1.removeMovieClip();
    mcRectangle2.removeMovieClip();
    mcRectangle3.removeMovieClip();
    tOutput.text = "One or more of the dimensions you specified is undefined.";
  }
  catch (oorObject:OutOfRangeError) {
    mcRectangle1.removeMovieClip();
    mcRectangle2.removeMovieClip();
    mcRectangle3.removeMovieClip();
    tOutput.text = "One or more of the dimensions you specified is out of range.";
  }
}

Of course the preceding example doesn't handle the different errors drastically differently. However, depending on the context, you could certainly handle different types of errors quite differently. For example, some errors may be types that require you to halt a process and alert the user. Other types of errors may be of the sort that you can handle them transparently by simply using some default value or running another function.

 

Using finally

There's yet another block you can optionally include with try/catch. A finally block can appear after the try and catch blocks, and it can contain code that is run regardless of what happens within the try and/or catch blocks. For example:

try {
  trace("try");
}
catch (errObject:Error) {
  trace("catch");
}
finally {
  trace("finally");
}

The preceding code will output:

try
finally

Since no error is thrown, the catch block doesn't run. However, the finally block does run.

Similarly, the finally block will run even when an error is thrown:

try {
  throw new Error();
  trace("try");
}
catch (errObject:Error) {
  trace("catch");
}
finally {
  trace("finally");
}

The preceding code outputs:

catch
finally

The trace("try"); is not run because it occurs after the error is thrown. But the finally block still runs.

The uses of finally are a little more obscure than the uses of try and catch. Obviously the following code accomplishes the same thing as the preceding, but without a finally block:

try {
  throw new Error();
  trace("try");
}
catch (errObject:Error) {
  trace("catch");
}
trace("finally");

Using the finally block does tend to make the code somewhat more readable. However, there are some functional differences between using a finally block and not using a finally block that can be seen in certain contexts. For example, consider the following:

function someFunction():Void {
  try {
    throw new Error();
    trace("try");
  }
  catch (errObject:Error) {
    trace("catch");
    return;
  }
  finally {
    trace("finally");
  }
}

If you call someFunction(), then you will see that the following is displayed in the Output panel:

catch
finally

That is because the finally block runs regardless of what happens in the try or catch blocks. So even though the catch block has a return statement that exits the function, the finally block runs. Compare that with the following:

function someFunction():Void {
  try {
    throw new Error();
    trace("try");
  }
  catch (errObject:Error) {
    trace("catch");
    return;
  }
  trace("finally");
}

The preceding code will simply display:

catch

The finally is not displayed because the function is exited by the return statement in the catch block.

Conclusion

In this article you've had a chance to learn about the basics of implementing try/catch in your ActionScript code. Using try/catch is a good practice to start using, particularly when building libraries of classes. When appropriate, your methods should throw errors. Make sure to document the errors that methods can throw so that they can be properly handled.

Approximate download size: 128k