Try OpenEdge Now
skip to main content
Error Handling
Handling Errors with CATCH Blocks : Flow-of-control statements in a CATCH block
 

Flow-of-control statements in a CATCH block

The code in any CATCH block can contain an explicit flow-of-control directive, including LEAVE, NEXT, RETRY, RETURN, or THROW. If you use RETRY or THROW, then the UNDO option is required. Since CATCH is an undoable block, LEAVE, NEXT, and RETRY without a label applies to the CATCH block itself and not the associated block.
If you want LEAVE, NEXT, or RETRY to apply to the associated block of a CATCH block, you must use the label syntax for these statements.
An explicit UNDO, THROW in a CATCH block causes the AVM to raise ERROR in the block that encloses the associated block of the CATCH block; not the associated block itself.
In this example, LEAVE in the CATCH applies to the CATCH:
DEFINE VARIABLE iOrdNum AS INTEGER.
DEFINE VARIABLE lSomeCondition AS LOGICAL.

FOR EACH Customer:
UPDATE iOrdNum.
FIND Order WHERE Order.CustNum = Customer.CustNum
AND Order.OrderNum = iOrdNum. /* Can fail and raise ERROR */

DISPLAY Order.OrderNum Order.ShipDate. /* don't get here if FIND fails */

CATCH eSysError AS Progress.Lang.SysError:
MESSAGE "Order " iOrdNum " does not exist for Customer ".

/* This LEAVE applies to the CATCH.
Execution will retry the same customer */
IF lSomeCondition THEN DO:
UNDO, LEAVE.
END.
...
/* more statements in the CATCH that will execute if UNDO, LEAVE
didn't execute */
END CATCH.
END. /* FOR EACH Customer */
In this example, the procedure gives the user three chances to get the right order number:
DEFINE VARIABLE iOrdNum as INTEGER.
DEFINE VARIABLE iTries as INTEGER.

ASSIGN iTries = 1.

/* Associated block has a label */
blk1:
FOR EACH Customer:

/* User input statement means default error handling is UNDO, RETRY */
DISPLAY Customer.Name.
UPDATE iOrdNum.

/* Will fail and raise ERROR if user enters invalid Order number */
FIND Order WHERE Order.CustNum = Customer.CustNum
AND Order.OrderNum = iOrdNum.
...
CATCH eSysError AS Progress.Lang.SysError:
        MESSAGE "Order " iOrdNum " does not exist for this Customer.".

/* Note that iTries is not reset on UNDO of the FOR EACH because it
is not referenced in the FOR EACH block. */
IF iTries <= 3 THEN DO:
ASSIGN iTries = iTries + 1.
END.
ELSE DO:
MESSAGE "Too many tries for this Customer".
ASSIGN iTries = 1.
/* Leave the CATCH. Execution will resume with the NEXT
iteration of the FOR EACH */
UNDO, NEXT blk1.
END.
END CATCH.
END. /* blk1 FOR EACH */
Because this example includes input from the user, the default error handling is RETRY for the FOR EACH block. The CATCH block overrides the RETRY option only if the user has failed three times to enter a correct Order number for a particular Customer. Then, the UNDO, LEAVE blk1 statement does not UNDO the associated block because that step has already occurred. The NEXT directive executes and the FOR EACH resumes execution with the next iteration of the block.
Note: Note that when you are working with nested blocks, there is a relationship between the scope of a LEAVE (or NEXT) directive and the scope of the UNDO. You cannot LEAVE or NEXT to a context outside the scope of the UNDO while the potential for the UNDO is in effect. If the UNDO action has already occurred, you can use LEAVE and NEXT to go to a higher context.
If there is no explicit flow-of-control statement in the CATCH block, the AVM will leave the CATCH block and execute the default error action for the associated block after executing the last statement in the CATCH block and any code within a FINALLY block. This means RETRY for all blocks. When no I/O blocking (user input) statements are present, the AVM prevents infinite looping by changing the RETRY to NEXT for iterating blocks or LEAVE for non-iterating blocks.
In the following code, if an Acme.Error.myAppError is caught the explicit UNDO, THROW statement causes the caught error to be thrown to the block enclosing the FOR EACH (remember that UNDO, THROW in a CATCH means leave the associated block, then THROW). However, if a Progress.Lang.SysError is caught the AVM will execute a NEXT on the FOR EACH block, as shown:
FOR EACH Customer:
    /* FOR EACH code */

    CATCH eSysError AS Progress.Lang.SysError:
        /* Handler code for SysError condition */

       /* RETRY on FOR EACH after leaving the CATCH, which becomes NEXT if
          there are no I/O statements.*/

    END CATCH.

    CATCH myAppErr AS Acme.Error.myAppError:
        /* Handler code for myAppError condition */

        UNDO, THROW myAppErr. /* THROW error to block enclosing FOR EACH */
    END CATCH.
END.