Previous Page
Next Page

19.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 chain of handler calls (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 such block catches the error, it percolates all the way up to AppleScript, and the script terminates prematurely (possibly with an error dialog ).

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, interrupting 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 even throw an error deliberately 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 need not kill your script. It will if you let it, and this can be a good thing (because Cancel often means "stop"). But alternatively, your script can catch the error as a way of learning that the user has cancelled, and can then proceed in some other appropriate manner.

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

19.7.1. Throwing an Error

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 the default values of the parameters:


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 percolate 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 19-1 shows how that error is presented to the user in the Script Editor.

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 ("Applet Options" in Chapter 27), 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 numberlet's say it's 32the dialog reads: "An error of type 32 has occurred."

Figure 19-1. An error dialog


An error number is not highly communicative to the user, unless the user happens to have 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.

Error number -128 is special. If an error with this number percolates up to AppleScript, the script stops but no error dialog is displayed. This is the error number generated when the user presses Cancel in a dialog presented by an interface scripting addition command such as display dialog. Thus, the user can cancel without automatically being presented with an error message immediately after. ("User canceled." "I know that!")

Some milieus never present an error message, regardless of the error. For example, if a script being run by Apple's Script Menu generates a runtime error, the script will simply fail silently. I regard this as a bug.

19.7.2. Catching an Error

The only way to catch an error is for that error to be thrown within a try block . The thrown error percolates up through the call chain , and if it eventually finds itself within a try block, it may be caught. The point of the call chain here is that the error need not occur directly within a try block; it may occur within in a handler that was called within a try block, or the call to that handler may be within a try block, and so forth.

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

try
    -- code in which errors will be caught
end try

This form of try block handles the error by ignoring it. If an error occurs within 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

Without the try block, this code would never reach the last line after the user presses the Cancel button.

In this next example, we use a try block as a form of flow control, to terminate a loop prematurely (see "Looping," earlier in this chapter). 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 inside a try block. The loop is ostensibly endless, but in actual fact, 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
L -- {"feathers", "gromit", "Network"}

In the second, fuller form of try block, you supply a second block, an error block , presumably containing some error-handling functionality:

try
    -- code in which errors will be caught
on error [parameters]
    -- error-handling code
end try

If an error is occurs within the try block (the part before the error block), the try block terminates; execution resumes at the start of the error block. If no error occurs in the try block, the error block is skipped.

If an error occurs within the error block, it is not caught by this try block, because we are past that already; but it might be caught by some other try block that we are nested inside, either directly or further up the calling chain. Indeed, it is perfectly legitimate, and possibly useful, to throw an error within an error block.

The parameters of an error block 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 .

A not-uncommon technique is to include all the parameters and rethrow the very same error, or a slightly modified version of it, from within the error block. This could be a way, for instance, to shut things down in good order before letting the error percolate all the way to AppleScript and display a message. It can also be a way to tell yourself more about where the error occurred:

on num(what)
    try
        return what as number
    on error s number i partial result p from f to t
        set s to "Handler num got an error: " & s
        error s number i partial result p from f to t
    end try
end num
num("howdy") -- error: Handler num got an error: Can't make "howdy" into type number

Error handling and error throwing can be the basis of useful flow control. You can do some powerful things with errors that can't easily be accomplished in any other way. You can also structure your scripts better through the use of errors.

In this example, flow control is implemented entirely through handler calls and errors. We ask the user for a number; if the user tries to cancel, or supplies something that can't be coerced to a number, AppleScript throws an error, and we start over recursively. The error thrown at the end of the askUser handler is a trick for returning the user's number directly without unwinding the entire recursion:

on askUser( )
    try
        set x to text returned of 
(display dialog "Give me a number:" default answer "") set x to (x as number) on error askUser( ) end try error x end askUser try askUser( ) on error what display dialog "Your number is " & what end try

This next example is somewhat similar: it asks the user to enter the name of a color, and persists until the user complies. (In this example, unlike the previous one, if the user cancels, the script politely stops.) The example demonstrates how errors and error handling can help you organize the structure of a script:

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?"

In that example, the handler getFavoriteColor has just one jobto try to get the user's favorite color and report what happened. Either it returns the user's favorite color, or it throws error 1000 to signal that the user left the field blank in the dialog, or it throws error 1001 to signal that the user cancelled. It's up to the caller to decide how to proceed based on on this report. This particular caller has a different dialog ready to show the user in case of either error, and 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 yoursthose that you expect and are prepared to deal with. Unexpected errors are simply allowed to percolate on up the call chain, possibly all the way 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 errorfor example, the user enters text in the dialog that can't be coerced to a numberwe rethrow it and let AppleScript inform the user that this is not an integer.

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 entire error block from the previous example in a much briefer form, 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. To illustrate, here's the same example again, but this time we'll catch both error -1728 (no such disk) and error -1700 (not an integer).

set n to text returned of 
(display dialog "What disk would you like the name of?" default answer "") try try tell application "Finder" to set x to name of disk (n as integer) display dialog x on error e number -1728 display dialog "I don't think that disk exists. " & e end try on error e number -1700 display dialog "I don't think that was an integer." end try

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

on askUser( )
    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 -1728 display dialog "I don't think that disk exists. " & e end try end askUser try askUser( ) on error e number -1700 display dialog "I don't think that was an integer." end try

An expired timeout (see "Timeout," 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


Previous Page
Next Page