Defines an error-handling end block for any undoable ABL block. An end block is an ABL block that can occur only within another block. The block containing the end block is known as the associated block. End-blocks must occur between the last line of executable code in the associated block and the END statement.
The CATCH end block executes when an error raised in the associated block is compatible with the error type specified in the CATCH block. To be compatible, the error type must be the error type specified in the CATCH statement, or it must be a sub-type (sub-class) of the specified type. CATCH blocks take precedence over any implicit or explicit ON ERROR directives for the associated block. This is the syntax for the CATCH statement and its related blocks:
block-statements CATCH error-variable AS [ CLASS ]error-class: catch-logic END [ CATCH ] . [block-end-statement] |
The following code fragment shows CATCH blocks for associated DO blocks:
DO ON ERROR UNDO, LEAVE: FIND FIRST Customer NO-LOCK WHERE Customer.CustNum = 5000. CATCH oneError AS Progress.Lang.SysError: MESSAGE oneError:GetMessage(1) VIEW-AS ALERT-BOX BUTTONS OK. END CATCH. CATCH twoError AS Progress.Lang.ProError: MESSAGE twoError:GetMessage(1) VIEW-AS ALERT-BOX BUTTONS OK. END CATCH. END. /* FIRST DO */ DO ON ERROR UNDO, LEAVE: FIND FIRST Customer NO-LOCK WHERE Customer.CustNum = 6000. /* You can reuse an error-variable from a different associated block */ CATCH oneError AS Progress.Lang.SysError: MESSAGE oneError:GetMessage(1) VIEW-AS ALERT-BOX BUTTONS OK. END CATCH. /* NOT LEGAL: Each CATCH block in an associated block must have a unique error-variable. */ CATCH oneError AS Progress.Lang.ProError: MESSAGE oneError:GetMessage(1) VIEW-AS ALERT-BOX BUTTONS OK. END CATCH. END. /* SECOND DO */ |
In the following example, the CATCH block will catch any ABL system error:
DEFINE VARIABLE iCust AS INTEGER NO-UNDO INITIAL 5000. FIND Customer NO-LOCK WHERE Customer.CustNum = iCust. /* Will fail */ /* Won't execute because FIND fails */ MESSAGE "Customer found" VIEW-AS ALERT-BOX BUTTONS OK. /* The associated block for this CATCH block is the main block of the .p */ CATCH eSysError AS Progress.Lang.SysError: MESSAGE eSysError:GetMessage(1) VIEW-AS ALERT-BOX BUTTONS OK. END CATCH. |
associated-block: . . . [ CATCH options : . . . END [ CATCH ] . ] ... [ FINALLY : . . . END [ FINALLY ] . ] END. /* associated-block */ |
Note that a CATCH block can also contain a CATCH or FINALLY block, just as a FINALLY block can contain a CATCH or FINALLY block. For more information on FINALLY blocks, see the FINALLY statement reference entry.
Thus, the following blocks can have a CATCH block:
FOR EACH Customer: /* Code body of the associated block */ /* This CATCH specifies the most specialized user-defined error class. It will catch only myAppError error objects or objects derived from myAppError. */ CATCH eMyAppError AS Acme.Error.myAppError: /*Handler code for Acme.Error.myAppError condition. */ END CATCH. /* This CATCH will handle Progress.Lang.AppError or any user-defined application error type, except for eMyAppError which would be handled by the preceding CATCH block. */ CATCH eAppError AS Progress.Lang.AppError: /* Handler code for AppError condition. */ END CATCH. /* This CATCH will handle any error raised by an ABL statement. Since it inherits from the same object as AppError in the class hierarchy, this CATCH could come before or after the CATCH for AppError */ CATCH eSysError AS Progress.Lang.SysError: /* Handler code for SysError condition. */ END CATCH. /* This will catch any possible error raised in the ABL. */ CATCH eError AS Progress.Lang.Error: /* Handler code for any error condition. */ END CATCH. END. /* FOR EACH Customer, associate block */ |
FOR EACH Customer: /* Code body of the associated block. */ /*This will catch all application errors */ CATCH eAppError AS Progress.Lang.AppError: /* Handler code for AppError condition */ END CATCH. /* Never get here, because myAppError is a subtype of Progress.Lang.AppError */ CATCH eMyAppError AS Acme.Error.myAppError: /* Handler code for myAppError condition */ END CATCH. END. /* FOR EACH Customer, Associated Block */ |
If error is raised in a block and is not handled by a CATCH block, then the error is handled by the ON ERROR directive of the associated block. This could be an explicit ON ERROR phrase, or the implicit (default) ON ERROR directive for the block type.
FOR EACH Customer: /* FOR EACH code body */ DO ON ERROR UNDO, LEAVE: /* DO code body */ CATCH eAppError AS Progress.Lang.AppError: /* CATCH code body */ CATCH eSysError AS Progress.Lang.SysError: UNDO, THROW eSysError. /* Will be handled by CATCH anyError... */ END CATCH. END CATCH. END. /* DO */ CATCH anyError AS Progress.Lang.Error: /* Handler code for anyError condition */ END CATCH. END. /* FOR EACH Customer */ |
The same behavior occurs for an explicit UNDO, THROW statement in a CATCH block. For example:
DO ON ERROR UNDO, LEAVE: /* Check for Orders */ /* Fails and throws Progress.Lang.SysError. Execution goes to CATCH */ FIND FIRST Order NO-LOCK WHERE Order.CustNum = 1000. MESSAGE "Order found". /* MESSAGE does not execute */ CATCH eSysError AS Progress.Lang.SysError: /* Check if Customer exists, which fails. ON ERROR UNDO, THROW for CATCH will raise ERROR in main block of .p - execution goes to CATCH in main block */*/ FIND FIRST Customer NO-LOCK WHERE Customer.CustNum = 1000. END CATCH. END. MESSAGE "Customer found". /* MESSAGE does not execute */ /* This CATCH is for the main block of the .p */ CATCH eSysError AS Progress.Lang.SysError: MESSAGE eSysError:GetMessage(1) VIEW-AS ALERT-BOX BUTTONS OK. END CATCH. |
In this example, DO TRANSACTION and CATCH both reference the Customer buffer:
/* myproc.p */ DEFINE VARIABLE myInt as INTEGER NO-UNDO INITIAL 5. MESSAGE myInt AVAILABLE(Customer). /* will see '5 no'*/ DO TRANSACTION ON ERROR UNDO, LEAVE: myInt = 10. FIND FIRST Customer NO-LOCK. MESSAGE myInt AVAILABLE(Customer). /* will see '10 yes' */ /* Returns ERROR (throws Progress.Lang.AppError). The block is undone and the Customer buffer is released - execution goes to CATCH. */ RUN Test.p (Customer.CustNum). ... CATCH eAppError AS Progress.Lang.AppError: MESSAGE myInt AVAILABLE(Customer). /* will see '5 no'*/ END CATCH. END. |
As the result of the reference to the Customer buffer in the CATCH block in the previous example, the scope of the Customer buffer is raised to the procedure level (myproc.p), since the smallest enclosing block of the DO TRANSACTION is the procedure block.
If you want LEAVE, NEXT, or RETRY to apply to the associated block of a CATCH block, you must use the existing 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 NO-UNDO. DEFINE VARIABLE lSomeCondition AS LOGICAL NO-UNDO. FOR EACH Customer: UPDATE iOrdNum. FIND Order NO-LOCK WHERE Order.CustNum = Customer.CustNum AND Order.OrderNum = iOrdNum. /* Can fail and raise ERROR */ /* Won't get here if FIND fails */ DISPLAY Order.OrderNum Order.ShipDate. 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 UNDO, LEAVE. ... /* 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 NO-UNDO. DEFINE VARIABLE iTries AS INTEGER NO-UNDO INITIAL 1. blk1: FOR EACH Customer NO-LOCK: UPDATE iOrdNum. FIND Order NO-LOCK WHERE Order.CustNum = Customer.CustNum AND Order.OrderNum = iOrdNum. /* Can Fail and raise ERROR */ ... CATCH eSysError AS Progress.Lang.SysError: MESSAGE "Order " iOrdNum " does not exist for Customer ". IF iTries <= 3 THEN iTries = iTries + 1. ELSE DO: MESSAGE "Too many tries for this Customer". iTries = 1. /* Leave the CATCH. Execution will resume with the next iteration of the FOR EACH */ UNDO, LEAVE blk1. END. END CATCH. END. /* FOR EACH Customer */ |
In this example, LEAVE the FOR EACH in the occurrence of a PrinterDown application error:
DEFINE VARIABLE iOrdNum AS INTEGER NO-UNDO. DEFINE VARIABLE iTries AS INTEGER NO-UNDO INITIAL 1. blk1: FOR EACH Customer NO-LOCK: UPDATE iOrdNum. /* Can Fail and raise ERROR - execution will go to CATCH for Progress.Lang.SysError */ FIND Order NO-LOCK WHERE Order.CustNum = Customer.CustNum AND Order.OrderNum = iOrdNum. /* Successfully found Order. Try to print invoice. If PrintInvoice.p throws an Acme.Error.PrinterDownError error, just leave the FOR EACH block. If PrintInvoice.p throws any other type of AppError, try with the next customer. */ RUN PrintInvoice.p (INPUT Order.OrderNum). ... CATCH pde AS Acme.Error.PrinterDownError: MESSAGE "Printer down...aborting". UNDO, LEAVE blk1. END CATCH. CATCH eAppError AS Progress.Lang.AppError: MESSAGE "Problem printing invoice for order " iOrdNum. /* Leave the CATCH. Execution will resume with the next iteration of the FOR EACH */ UNDO, NEXT blk1 END CATCH. END. /* FOR EACH Customer */ |
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 input-blocking 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 associated block, then throw). However, if a Progress.Lang.SysError is caught the AVM will execute a NEXT on the FOR EACH block. For example:
FOR EACH Customer ON ERROR UNDO, LEAVE: /* FOR EACH code */ CATCH eSysError AS Progress.Lang.SysError: /* Handler code for SysError condition */ /* RETRY on FOR EACH after leaving the CATCH, which becomes LEAVE if there are no I/O staements.*/ END CATCH. CATCH myAppErr AS Acme.Error.myAppError: /* Handler code for myAppError condition */ /* THROW error to block enclosing the FOR EACH */ UNDO, THROW myAppErr. END CATCH. END. |
In this example, a CATCH handles the error and the error message is suppressed:
DEFINE VARIABLE myInt as INTEGER NO-UNDO INITIAL 5. DO ON ERROR UNDO, LEAVE: /* Raises ERROR and throws Progress.Lang.SysError. Error message suppressed and execution goes to CATCH */ FIND Customer 1000. MESSAGE "After Find". /* Will not get here */ CATCH eSysError AS Progress.Lang.SysError: /* Will display "** Customer record not on file. (138)" */ MESSAGE eSysError:GetMessage(1) VIEW-AS ALERT-BOX. /* Leave the CATCH, then the DO block */ END CATCH. END. |
In this example, there is no CATCH block that handles the error and the error message is not suppressed:
DEFINE VARIABLE myInt as INTEGER NO-UNDO INITIAL 5. DO ON ERROR UNDO, LEAVE: /* Raises ERROR and displays "Customer record not on file. (138)" UNDO, LEAVE the block */ FIND Customer 1000. MESSAGE "After Find". /* Won't get here */ CATCH ae AS Progress.Lang.AppError: MESSAGE ae:GetMessage(1) VIEW-AS ALERT-BOX. END CATCH. END. |