Raise ERROR to the caller of a user-defined function

The user-defined function, defined by the FUNCTION statement, returns a value of a specific data type as its primary function. The RETURN statement is used in the function body to specify what value to return to the caller. The RETURN ERROR statement is not used in the same way as it is in other blocks since it does not raise ERROR in the caller. Instead, it sets the target variable of the function to the Unknown value (?). Therefore, you can perform error checking on a function call by checking for the Unknown value (?). This technique only works if the target variable has a value other than the Unknown value (?) before the function is called.

The following code demonstrates this behavior:

DEFINE VARIABLE iFuncReturn AS INTEGER INITIAL 99 NO-UNDO. 

FUNCTION ErrorTest RETURNS INTEGER:
    RETURN ERROR. 
END FUNCTION.

ASSIGN iFuncReturn = ErrorTest(). 

IF iFuncReturn EQ ? THEN
    DISPLAY "Error in user-defined function.".

If you specify a string (RETURN ERROR <string>), the string is not seen as a RETURN-VALUE, but as the value being returned from the function. Therefore, if the function is defined to return a type other than CHARACTER, you get a compiler error (or a runtime error if the expression type is indeterminate at compile time). If the function is defined to return a CHARACTER, the code runs, but RETURN-VALUE is not set. The specified string is lost.

Structured error handling provides a more consistent and robust way to raise ERROR from user-defined functions using the UNDO, THROW statement rather than RETURN ERROR. There are two ways to do this:

  • Syntactically, you cannot use an ON ERROR phrase on a FUNCTION definition. Therefore, use the ROUTINE-LEVEL ON ERROR UNDO, THROW (or BLOCK-LEVEL ON ERROR UNDO, THROW) statement to set that directive on the function. Then any unexpected error, or explicitly thrown application errors, can be thrown back to the caller. Just be aware that this affects all routines or blocks in the file.
  • Add a CATCH block to the function. With a CATCH block, any error that is caught can be thrown out of the function using the UNDO, THROW statement from within the CATCH block. If you only want to throw application errors back to the caller but handle system errors locally, or vice versa, you can use multiple CATCH blocks to accomplish this. Using multiple CATCH blocks is shown in the example below. To learn more, see CATCH Blocks.
DEFINE VARIABLE iFuncReturn AS INTEGER INITIAL 99 NO-UNDO. 

FUNCTION ErrorTest RETURNS INTEGER:
   IF CurrentTime > ClosingTime THEN
      UNDO, THROW NEW Progress.Lang.AppError("Can't take a delivery order
                                              after closing time.", 1).
   CATCH err AS Progress.Lang.AppError:
      UNDO, THROW err. // Let the caller know
   END.

   CATCH err AS Progress.Lang.SysError:
      // Unexpected error; handle it here
      MESSAGE err:GetMessage(1) VIEW-AS ALERT-BOX.
   END.

END FUNCTION.

iFuncReturn = ErrorTest() NO-ERROR.
IF ERROR-STATUS:ERROR THEN
   MESSAGE "Error message returned from function:" SKIP 
            ERROR-STATUS:Get-Message(1).