Error Handling
There are literally hundreds of different errors that can occur while a procedure is running. Of course you’ll want to eliminate all of the errors in the procedure itself, but many errors are the result of circumstances beyond the programmer’s control. A file can fail to open because it was placed into the wrong folder, the user can enter the wrong data type into a formula; the list is endless. When such an error occurs, Panorama’s normal response is to display an error message and stop the procedure immediately. (In addition, if the procedure window is open, Panorama will attempt to highlight the statement which triggered the error.)

If you want to create a database that operates professionally, simply stopping the procedure half finished if there is an error may not be acceptable. Instead, you may want your procedure itself to trap the error and try to correct it, if possible. At a minimum, you may be able to display an error message that is more relevant to an untrained operator than Panorama’s general purpose error messages.

Panorama has several techniques for trapping errors, allowing you to “channel” the error in the direction you want it to go. The sections below describe each of these techniques. When you use these techniques to trap errors, Panorama has several functions that can be used to get information about the error that just occurred. To learn more about these functions see info(“error”), info(“errorstatement”), info(“errorparameter”), info(“errorstack”), and info(“executeerrorsource”).

#### IF ERROR

To trap errors in a particular statement, use the `if error` statement (two words - there must be a space). This statement must be placed immediately after the statement that you are worried might cause an error. For example, suppose you have a procedure that appends a file with the openfile statement. If the file is missing or has been moved an error will occur. This example checks for that error, and if the error occurs, asks the user to enter a new file name. The procedure will keep trying until the file is opened successfully or the user gives up and enters an empty name.

    local txFileName
    txFileName="New Transactions"
    loop
        openfile "+"+txFileName
        if error
            gettext "Enter the file name",txFileName
            repeatloopif txFileName<>""
        endif
    endloop
    if txFileName="" stop endif
    /* further processing of the new transactions, below */

`If error` must be used by itself, you cannot combine error with other conditions. For example, the statement:

    if error and info("modifiers") contains "shift" /* WILL NOT WORK !! */

will NOT work. To get this effect you must nest a second if statement inside the if error, like this.

    if error
        if info("modifiers") contains "shift"
            ...
        endif
    endif

#### TRY / CATCH

To trap errors in a sequence of statements use the try, catch and endcatch statements. These statements are used together like this:

    try
        ...
        ...
        ... one or more statements that may contain errors
        ...
        ...
    catch
        ...
        ... statements to handle error
        ...
    endcatch
    ...
    ... rest of program
    ...

After encountering the try statement, Panorama starts executing the statements that follow. If no errors occur in any of the statements, Panorama will jump from the catch to the first statement after the endcatch statement. But if there is an error in any of these statements, Panorama will jump immediately to the code just after catch statement. It will execute all of those statements, then continue with the statements after the endcatch statement. (Of course you can put a return or stop statement in between the catch and endcatch statements if you don’t want to continue.) This code can use the info(“error”) function if it needs to know what the error was that was trapped.

If the statements between try and catch include a call or execute statement (or any of the variations of these statements) then errors that occur within the subroutine (or even more deeply nested subroutines) will also trapped to the catch statement. If an error occurs within a subroutine Panorama will unwind any nested subroutines until it gets back to the procedure with the catch statement, then it will execute the statements after the catch statement.

Here is an example.

    try
        execute {Name="Jon"}
    catch
        growlmessage "Could not set Name"
    endcatch

If there is a field or global variable named Name, all is well. But if there isn’t, a growl notification will appear, then the program will continue. This is a bit contrived because there is really no reason for the execute statement here, but it illustrates that try/catch will catch errors within subroutines.

If you need to, you can nest try/catch statements inside each other. If an error occurs, the closest catch statement will handle it. Remember, however, that that catch statement could be in a subroutine that you cannot see at the moment (this would mean that you also cannot see the code that generated the error).

Note: You can use `endtry` instead of `endcatch` at the end of a try/catch block – both have exactly the same meaning.

#### Error Trapping with the ONERROR statement

The onerror statement can be used to catch all errors that are not trapped by if error statements. This has two benefits: 1) It allows the programmer to easily eliminate all error alert dialogs. This is very important for server applications because an alert dialog requires human intervention to get the server going again. 2) It makes it easy to build a log of errors. To learn more about this see the onerror statement.

#### Error Trapping with the _OnErrorCode Variable

In Panorama X 10.2 and later, you can put the error code into a windowglobal or fileglobal variable named `_OnErrorCode`. For example, you could add this line of code to the `.Initialize` procedure of a database:

    letfileglobal _OnErrorCode = {nsnotify info("error")}

With this code in place, Panorama will NEVER display an error alert if an error occurs in any procedure when this database is active. Instead, it will display a notification (see nsnotify). You could do whatever you want, ignore the error, log it to a file, send it to a server, it’s up to you. But with this code in place, Panorama will never display a standard error alert in this database. If you don’t want the user to ever see an alert (since they probably can’t do anything about it anyway), using `_OnErrorCode` is the way to go.

If you used a windowglobal variable instead of a fileglobal variable, the error trapping would only occur in that particular window, not for other windows in that database.

Note: When an error occurs, Panorama first checks to see if there is an applicable if error, try/catch, or onerror statement. If none of these are available it will fall back to the _OnErrorCode variable, if it exists. (If it doesn’t exist, it will simply display an alert or open the Error Wizard, depending on your preference settings.

Difference Between OnError statement and _OnErrorCode Variable These two techniques sound similar, but the difference is in the scope over which they take effect. The onerror statement traps errors within a single procedure. When the procedure ends, the error trapping also ends.

An OnErrorCode variable is linked to either a database or a window. It remains in effect as long as that database or window remains open, whether the original procedure is still running or not. If you want to stop the error trapping and return to normal Panorama error alerts, use the undefine statement to dispose of the variable.

#### THROWERROR

Sometimes you may want to generate an error yourself that can then be handled by catch or onerror. This can be done with the throwerror statement. In this example, any errors that happen between the try and catch statements will automatically be logged (assuming that you have a procedure called `logtheerror` that does this). But this will also happen if the account balance is negative.

    try
        ...
        ...
        if accountBalance<0
            throwerror "Error -- negative account balance"
        endif
        ...
        ...
    catch
        call logtheerror,info("error")
    endcatch
    ...
    ... continue here even if there is an error
    ...

You can also use this to give an error condition a more descriptive message, without having to write duplicate error handling code for every error you want to customize.

    try
        ...
        ...
        data = fileload("logfile"+datepattern(today(),"YYYYMMDD")+".log"
        if error
            throwerror "Today's log file is missing."
        endif
        ...
        ...
    catch
        call logtheerror,info("error")
    endcatch
    ...
    ... continue here even if there is an error
    ...

Note: In a formula, you can generate an error with the error( function.

See Also

- catcherror( -- evaluates an expression, but suppresses any error generated by that expression.
- Error Wizard -- Advanced dialog for displaying program errors.
- error( -- returns an error with the specified message.
- errortext( -- evaluates an expression, but returns only the error message generated (if any).
- executecatcherrors -- is the same as the execute statement, except for the fact that if an error occurs while running, it can be trapped by an if error statement immediately following the executecatcherrors statement.
- info("error") -- returns the most recent error message.
- info("errorparameter") -- returns the parameter that caused a runtime error.
- info("errorstack") -- returns the contents of the procedure stack after an error.
- info("errorstatement") -- returns the name of the statement that Panorama was trying to run at the time an error occurred.
- onerror -- can be used to catch all errors that are not trapped by if error or try statements.
- onfailedresume -- is used to setup a semi-graceful recovery if a resume statement fails because there was no pause statement.
- returnerror -- passes an error back to the current subroutines calling procedure.
- seterror -- changes the error message returned by info("error").
- throwerror -- causes an immediate error.

History

Version | Status | Notes
10.2 | Updated | Added the new _OnErrorCode variable for trapping errors in a specific database or window. This makes it possible to eliminate any chance of the user seeing an error alert in that database or window.
10.0 | Updated | Carried over from Panorama 6.0 but now includes the try/catch method for handling procedure errors.