Defines an ERROR or STOP condition-handling end block for any undoable ABL block. An end block is an ABL block that can occur only within and at the end of 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 its END statement.
A CATCH block executes when an ERROR or STOP condition raised in the associated block is compatible with the error or stop type that is specified in the CATCH statement. To be compatible, the error or stop type must be the type specified in the CATCH statement for an object that is thrown in the associated block, or it must be a sub-type (sub-class) of the specified type that is thrown. The CATCH block thus executes when a thrown object of the specified error or stop type is caught by the CATCH statement, at which point the object can then be processed (handled) in the CATCH block.
For ERROR condition-handling, CATCH blocks for error types (error CATCH blocks) take precedence over any implicit or explicit ON ERROR directives specified for the associated block. Similarly for STOP condition-handling, CATCH blocks for stop types (stop CATCH blocks) take precedence over any implicit or explicit ON STOP directives specified for the associated block.
This is the syntax for the CATCH statement and its related blocks:
block-statements CATCH error-or-stop-variable AS [ CLASS ]error-or-stop-class-type: 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-or-stop-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-or-stop-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 or a STOP condition raised by an unexpected 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. Note also that if the associated block does not handle a raised STOP condition with an ON STOP phrase or compatible CATCH block, or a raised QUIT condition with an ON QUIT phrase, the FINALLY block does not run. 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 */ |
Therefore, you might want the CATCH block to catch certain error or stop object types to handle directly, and have all other ERROR or STOP conditions handled by the ON ERROR UNDO or ON STOP UNDO directive of the associated block. Note also that with an ON ERROR or ON STOP phrase, the CATCH block does not have to catch a corresponding error or stop object type. That is, you can have an ON ERROR UNDO directive and a CATCH block that catches a stop object.
FOR EACH Customer: /* FOR EACH code body */ DO ON ERROR UNDO, LEAVE: /* Associated block of top-level CATCH */ /* DO code body */ CATCH eAppError AS Progress.Lang.AppError: /* CATCH eAppError code body raises a system error */ 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 */ |
Thus, a statement that raises ERROR or STOP within a CATCH block causes the following to occur:
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 or STOP in the block that encloses the associated block of the CATCH block; not the associated block itself.
In this example, there is a LEAVE of the FOR EACH bock blk1 for any occurrence of a PrinterDown application error and a NEXT to iterate the same FOR EACH block with the next Customer for any other application printer 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. 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". /* Leave the FOR EACH. */ LEAVE blk1. END CATCH. CATCH eAppError AS Progress.Lang.AppError: MESSAGE "Problem printing invoice for order " iOrdNum. /* Execution resumes with the next iteration of the FOR EACH */ NEXT blk1 END CATCH. END. /* FOR EACH Customer */ |
If there is no explicit flow-of-control statement in the CATCH block, the AVM leaves the CATCH block and executes the default ERROR or STOP 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 and the RETRY function is not used in the block, the AVM prevents infinite looping by changing the RETRY to NEXT for iterating blocks and to 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. Thus:
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 NEXT if there are no I/O statements and the RETRY function is not used in the block.*/ 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. |
Similar processing happens for a STOP condition raised because of a system error where a Progress.Lang.StopError object is thrown. Note that on an OpenEdge application server, the error messages added to the thrown Progress.Lang.StopError object are also written to the application server log file even though they are available to the client in the serialized object.
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. |