DekGenius.com
[ Team LiB ] Previous Section Next Section

12.7 Errors

An error is a message at runtime saying, in effect, that something bad has happened and execution cannot continue. The sender of such a message is said to throw an error. The message percolates up through the call chain looking for an error-handling block surrounding the line currently being executed; such a block is said to catch the error. If no block catches the error, it percolates all the way up to AppleScript, which puts up an error dialog, and the script terminates prematurely.

This entire mechanism is extremely nice, because it provides a target application, or AppleScript itself, with a way to signal that it's impossible to proceed and to interrupt the flow of code, while leaving it up to the caller whether and how to recover. Your script can implement no error handling, in which case any runtime error will bring the script to a grinding halt. Or your script can implement error handling in certain areas where it expects an error might occur. It can recover from some errors and re-throw others, allowing them to terminate the script. It can throw an error as a way of controlling the flow of code.

An error can be a positive thing, and can be built into the structure of a command's implementation. For example, display dialog throws an error if the user clicks the Cancel button in the dialog. This is not intended to kill your script. The expectation is that your script can just catch the error as a way of learning that the user has cancelled, and can then proceed in an appropriate manner.

I'll talk first about how to throw an error, then about how to catch one.

12.7.1 Throw

To throw an error, use the error command. It has five optional parameters:

error [messageString]
        [number shortInteger]
        [partial result list]
        [from anything]
        [to class]

Here are their default values:


messageString

Nothing


number

-2700


partial result

The empty list


from

The currently executing script or script object


to

The class item

You can use any of the parameters when throwing an error, but in real life you are likely to use only the first two. The others are present because this is also the structure of an error message from an application, which can supply this further information to help explain what the problem was.

If you throw an uncaught error, it will trickle all the way up to AppleScript and will be presented to the user as a dialog. The messageString is your chance to dictate what appears in that dialog. You will probably want to say something meaningful about what went wrong. For example:

error "Things fall apart, the centre cannot hold."

Figure 12-1 shows how that error is presented to the user in the Script Editor.

Figure 12-1. An error dialog
figs/as_1201.gif

If an error is thrown in an applet, the applet puts up a similar dialog, which also offers a chance to edit the script. If this is a Stay Open applet (Section 24.1.1), the error does not cause it to quit.

If you don't supply any parameters at all to your error command, the error dialog reads: "An error has occurred." If you don't supply a messageString but you do supply an error number—let's say it's 32—the dialog reads: "An error of type 32 has occurred."

An error number is not highly communicative to the user, unless the user is supplied with a table of error numbers and their meanings; but it is certainly useful within code, particularly when you're implementing internal error handling. If different kinds of things can go wrong, you can use this number to signal which one did go wrong. An example appears in the next section.

12.7.2 Catch

The only way to catch an error is for that error to be thrown within a try block; this includes code that is ultimately called by code within a try block. The thrown error percolates up through the calling chain, and if it eventually finds itself within a try block, it may be caught.

There are two forms of try block. In the first, there is no actual error-handling code:

try
        -- code
end try

This form of try block handles the error by ignoring it. If an error is caught anywhere in the try block, the block terminates; execution resumes after the end try, and that's the end of the matter. Thus, you have no way to learn directly that an error was caught (though you can learn indirectly, because some code may have been skipped). But at least the error didn't bring your script to a halt. Here's an example:

set x to "Cancel"
try
        set x to button returned of (display dialog "Press a button.")
end try
display dialog "You pressed " & x

If the user presses the Cancel button, display dialog throws an error; without the try block, this code would then never reach the last line.

In this next example, we use a try block as a form of flow control. We want to get the name of every disk. (Ignore the fact that we could just ask the Finder for this information directly.) Instead of asking how many disks there are and looping that number of times, we loop forever but inside a try block. When we exceed the number of disks, the Finder throws an error and the loop ends.

set L to {}
set x to 1
tell application "Finder"
        try
                repeat
                        set end of L to name of disk x
                        set x to x + 1
                end repeat
        end try
end tell

In the second form of try block, you supply some error-handling functionality:

try
        -- code
on error [parameters]
        -- error-handling code
end try

If an error is caught anywhere in the try part, the try part terminates; execution resumes at the start of the error block. If no error is caught anywhere in the try part, the error block is skipped. The parameters are exactly the same as those for an error command, so your error block can capture and respond to any information that may have been included when the error was thrown. You don't have to include any parameters and you can include any subset of the parameters; thus you aren't forced to capture information you don't care about. Parameter variable names are local to the error block.

In this example, we have a handler that returns one error code if the user cancels a dialog and another if the user fails to enter the required information in that dialog:

on getFavoriteColor(  )
        try
                set r to display dialog "What is your favorite color?" default answer ""
        on error
                error number 1001
        end try
        set s to text returned of r
        if s = "" then error number 1000
        return s
end getFavoriteColor
set c to ""
repeat until c is not ""
        try
                set c to getFavoriteColor(  )
        on error number n
                if n = 1000 then
                        display dialog "You didn't enter a color!" buttons "OK"
                else if n = 1001 then
                        display dialog "Why did you cancel? Tell me!" buttons "OK"
                end if
        end try
end repeat
display dialog "Aha, you like " & c & ", eh?"

This example illustrates how errors and error handling help with the distribution of responsibilities. The handler getFavoriteColor( ) has just one job—to get the user's favorite color. If something goes wrong, it signals this with an error; that's all. It's up to the caller to decide how to proceed at that point. In this case, the caller is prepared for the possibility that two kinds of thing might go wrong, and has a different dialog ready to show the user in each case. The caller is perfectly prepared to loop all day until the user enters something in the dialog. But all of that is the caller's own decision; the handler itself just performs the single task for which it was written. Distribution of responsibilities makes for more reusable code, and the example shows how throwing errors contributes to this.

A common technique in an error handler is to handle only those errors that are in some sense yours, those that you expect and are prepared to deal with. Unexpected errors are simply allowed to percolate on up to AppleScript, causing the script to terminate; this makes sense because they're unexpected and you're not prepared to deal with them. There are two ways to accomplish this.

One way is to catch all errors and then rethrow any errors you aren't prepared to handle. If you're going to do that, you should probably use all the parameters, both in the on error line as you catch the error and in the error command as you rethrow it; otherwise you might strip the error of some of its information, which might reduce its value to the user (or to any code at some higher level that catches it).

In this example, we ask the user for the number of a disk to get the name of. If the number is not the number of an existing disk, the Finder throws error number -1728, so if we get an error and that's its number, we deliver a meaningful response. If we get any other error—for example, the user enters text in the dialog that can't be coerced to a number—we rethrow it.

set n to text returned of ¬
        (display dialog "What disk would you like the name of?" default answer "")
try
        tell application "Finder" to set x to name of disk (n as integer)
        display dialog x
on error e number n partial result p from f to t
        if n = -1728 then
                display dialog "I don't think that disk exists. " & e
        else
                error e number n partial result p from f to t
        end if
end try

The other approach is to use a filtered error handler . In this approach, some of the parameters in the on error line are not variable names but literals. AppleScript will call the error block only if all such literals are matched by the corresponding error parameter value. Otherwise, the error percolates up the call chain, of its own accord.

Thus, we can rewrite the error block from the previous example as follows:

on error e number -1728
        display dialog "I don't think that disk exists. " & e
end try

There's no way to list alternative literals; you can't write an error block that catches errors with either of just two particular error numbers, for instance. A workaround is to nest try blocks. Thus we can rewrite the second half of the earlier "favorite color" example like this:

set c to ""
repeat until c is not ""
        try
                try
                        set c to getFavoriteColor(  )
                on error number 1000
                        display dialog "You didn't enter a color!" buttons "OK"
                end try
        on error number 1001
                display dialog "Why did you cancel? Tell me!" buttons "OK"
        end try
end repeat
display dialog "Aha, you like " & c & ", eh?"

If you don't like the look of literally nested try blocks ("lexical nesting"), you can nest them by means of the calling chain ("dynamic nesting"):

global c
set c to ""
on askUser(  )
        try
                set c to getFavoriteColor(  )
        on error number 1000
                display dialog "You didn't enter a color!" buttons "OK"
        end try
end askUser
repeat until c is not ""
        try
                askUser(  )
        on error number 1001
                display dialog "Why did you cancel? Tell me!" buttons "OK"
        end try
end repeat
display dialog "Aha, you like " & c & ", eh?"

An expired timeout (Section 12.5.1, earlier in this chapter) is an error like any other; this example shows a way to handle it:

try
        tell application "Finder"
                activate
                with timeout of 1 second
                        display dialog "Press a button." giving up after 2
                end timeout
        end tell
on error number -1712
        activate
        display dialog "Ha ha, not fast enough!"
end try
    [ Team LiB ] Previous Section Next Section