Handling errors in UDFs



ColdFusion provides several techniques to handle errors in UDFs:

  • Display error messages directly in the function.

  • Return function status information to the calling page.

  • Use try/catch or cftry/cfcatch blocks and the cfthrow and cfrethrow tags to handle and generate exceptions.

The technique you use depends on the circumstances of your function and application and on your preferred programming style. However, it is best for most functions to use the second or third technique, or a combination of the two.

Displaying error messages

Your function can test for errors and use the WriteOutput function to display an error message directly to the user. This method is useful for providing immediate feedback to users for simple input errors. You can use it independently or in conjunction with either of the other two error-handling methods.

For example, the following variation on a “Hello world” function displays an error message if you do not enter a name in the form:

<cfform method="POST" action="#CGI.script_name#"> 
    <p>Enter your Name:&nbsp; 
    <input name="name" type="text" hspace="30" maxlength="30"> 
    <input type="Submit" name="submit" value="OK"> 
</cfform> 
<cfscript> 
    function HelloFriend(Name) { 
        if (Name is "") WriteOutput("You forgot your name!"); 
        else WriteOutput("Hello " & name &"!"); 
        return ""; 
    } 
    if (IsDefined("Form.submit")) HelloFriend(Form.name); 
</cfscript>

Reviewing the code

The following table describes the code:

Code

Description

<cfform method="POST" action="#CGI.script_name#"> 
<p>Enter your Name:&nbsp; 
<input name="name" type="text" hspace="30" 
maxlength="30"> 
<input type="Submit" name="submit" value="OK"> 
</cfform>

Creates a simple form requesting you to enter your name.

Uses the script_name CGI variable to post to this page without specifying a URL.

If you do not enter a name, the form posts an empty string as the name field.

<cfscript> 
function HelloFriend(Name) { 
if (Name is "") WriteOutput("You forgot your  name!"); 
else WriteOutput("Hello " & name &"!"); return ""; 
} 
if (IsDefined("Form.submit")) 
HelloFriend(Form.name); 
</cfscript>

Defines a function to display "Hello name!" First, checks whether the argument is an empty string. If so, displays an error message.

Otherwise displays the hello message.

Returns the empty string. (The caller does not use the return value). It is not necessary to use curly brackets around the if or else statement bodies because they are single statements.

If this page has been called by submitting the form, calls the HelloFriend function. Otherwise, the page just displays the form.

Providing status information

In some cases, such as those where the function cannot provide a corrective action, the function cannot, or should not, handle the error directly. In these cases, your function can return information to the calling page. The calling page must handle the error information and act appropriately.

Consider the following mechanisms for providing status information:

  • Use the return value to indicate the function status only. The return value can be a Boolean success/failure indicator. The return value can also be a status code, for example where 1 indicates success, and various failure types are assigned known numbers. With this method, the function must set a variable in the caller to the value of a successful result.

  • Set a status variable that is available to the caller (not the return variable) to indicate success or failure and any information about the failure. With this method, the function can return the result directly to the caller. In this method, the function uses only the return value and structure arguments to pass the status back to the caller.

Each of these methods can have variants, and each has advantages and disadvantages. The technique that you use depends on the type of function, the application in which you use it, and your coding style.

The following example, which modifies the function used in A user-defined function example, uses one version of the status variable method. It provides two forms of error information:

  • It returns -1, instead of an interest value, if it encounters an error. This value can serve as an error indicator because you never pay negative interest on a loan.

  • It also writes an error message to a structure that contains an error description variable. Because the message is in a structure, it is available to both the calling page and the function.

The TotalInterest function

After changes to handle errors, the TotalInterest function looks like the following. Code that is changed from the example in A user-defined function example is in bold.

<cfscript> 
function TotalInterest(principal, annualPercent, months, status) { 
    Var years = 0; 
    Var interestRate = 0; 
    Var totalInterest = 0; 
    principal = trim(principal); 
    principal = REReplace(principal,"[\$,]","","ALL"); 
    annualPercent = Replace(annualPercent,"%","","ALL"); 
    if ((principal LE 0) OR (annualPercent LE 0) OR (months LE 0)) { 
        Status.errorMsg = "All values must be greater than 0"; 
        Return -1; 
    } 
    interestRate = annualPercent / 100; 
    years = months / 12; 
    totalInterest = principal*(((1+ interestRate)^years)-1); 
    Return DollarFormat(totalInterest); 
} 
</cfscript>

Reviewing the code

The following table describes the code that has been changed or added to the previous version of this example. For a description of the initial code, see A user-defined function example.

Code

Description

function TotalInterest(principal, annualPercent, months, status)

The function now takes an additional argument, a status structure. Uses a structure for the status variable so that changes that the function makes affect the status structure in the caller.

if ((principal LE 0) OR (annualPercent LE 0) OR (months LE 0)) { 
Status.errorMsg = "All values must be greater than 0"; 
Return -1; 
}

Checks to make sure the principal, percent rate, and duration are all greater than zero.

If any is not, sets the errorMsg key (the only key) in the Status structure to a descriptive string. Also, returns -1 to the caller and exits the function without processing further.

Calling the function

The code that calls the function now looks like the following. Code that is changed from the example in A user-defined function example is in bold.

<cfset status = StructNew()> 
<cfset myInterest = TotalInterest(Form.Principal,  
    Form.AnnualPercent,Form.Months, status)> 
<cfif myInterest EQ -1> 
    <cfoutput> 
        ERROR: #status.errorMsg#<br> 
    </cfoutput> 
<cfelse> 
    <cfoutput>  
        Loan amount: #Form.Principal#<br> 
        Annual percentage rate: 
            #Form.AnnualPercent#<br> 
        Loan duration: #Form.Months# months<br> 
        TOTAL INTEREST: #myInterest#<br> 
    </cfoutput> 
</cfif>

Reviewing the code

The following table describes the code that has been changed or added:

Code

Description

<cfset status = StructNew()>

Creates a structure to hold the function status.

<cfset myInterest = TotalInterest 
(Form.Principal, Form.AnnualPercent, 
Form.Months, status)>

Calls the function. This time, the function requires four arguments, including the status variable.

<cfif myInterest EQ -1> 
<cfoutput> 
ERROR: #status.errorMsg#<br> 
</cfoutput>

If the function returns -1, there must be an error. Displays the message that the function placed in the status.errorMsg structure key.

<cfelse> 
<cfoutput> 
Loan amount: #Form.Principal#<br> 
Annual percentage rate: 
#Form.AnnualPercent#<br> 
Loan duration: #Form.Months# months<br> 
TOTAL INTEREST: #myInterst#<br> 
</cfoutput> 
</cfif>

If the function does not return -1, it returns an interest value. Displays the input values and the function return value.

Using exceptions

UDFs written in CFScript can handle exceptions using the try and catch statements. UDFs written using the cffunction tag can use the cftry, cfcatch, cfthrow, and cfrethrow tags. Using exceptions corresponds to the way many functions in other programming languages handle errors, and can be an effective way to handle errors. In particular, it separates the functional code from the error-handling code, and it can be more efficient than other methods at runtime, because it does not require testing and branching.

Exceptions in UDFs have the following two dimensions:

  • Handling exceptions generated by running the UDF code

  • Generating exceptions when the UDF identifies invalid data or other conditions that would cause errors if processing continued

Handling exceptions in UDFs

Use try/catch blocks to handle exceptions in a UDF under the same conditions that any other ColdFusion application uses try/catch blocks. These conditions are typically circumstances where the function uses an external resource, such as a Java, COM, or CORBA object, a database, or a file. When possible, have your application prevent, rather than catch, exceptions caused by invalid application data. For example, the application can prevent users from entering a zero value for a form field that is used to divide another number, rather than handling exceptions generated by dividing by zero.

When ColdFusion catches an exception, the function can use any of the following methods to handle the exception:

  • If the error is recoverable (for example, if the problem is a database time-out where in some cases retrying resolves the issue), try to recover from the problem.

  • Display a message, as described in Displaying error messages.

  • Return an error status, as described in Providing status information.

  • If the UDF is defined using the cffunction tag, throw a custom exception, or rethrow the exception so that it the calling ColdFusion page catches it. For more information on throwing and rethrowing exceptions, see Handling runtime exceptions with ColdFusion tags.

Generating exceptions in UDFs

If you define your function using the cffunction tag, you can use the cfthrow and cfrethrow tags to throw errors to the page that called the function. You can use this technique whenever your UDF identifies an error, instead of displaying a message or returning an error status. For example, the following code rewrites the example from Providing status information to use the cffunction tag and CFML, and to throw and handle an exception if any of the form values are not positive numbers.

The lines that identify invalid data and throw the exception are in bold. The remaining lines are equivalent to the CFScript code in the previous example. However, the code that removes unwanted characters must precede the error checking code.

<cffunction name="TotalInterest"> 
    <cfargument name="principal" required="Yes"> 
    <cfargument name="annualPercent" required="Yes">  
    <cfargument name="months" required="Yes"> 
    <cfset var years = 0> 
    <cfset var interestRate = 0> 
    <cfset var totalInterest = 0> 
 
    <cfset principal = trim(principal)> 
    <cfset principal = REReplace(principal,"[\$,]","","ALL")> 
    <cfset annualPercent = Replace(annualPercent,"%","","ALL")> 
     
    <cfif ((principal LE 0) OR (annualPercent LE 0) OR (months LE 0))> 
        <cfthrow type="InvalidData" message="All values must be greater than 0."> 
    </cfif> 
 
    <cfset interestRate = annualPercent / 100> 
    <cfset years = months / 12> 
    <cfset totalInterest = principal* 
            (((1+ interestRate)^years)-1)> 
    <cfreturn DollarFormat(totalInterest)> 
</cffunction>

The code that calls the function and handles the exception looks like the following. The changed lines are in bold.

<cftry> 
    <cfset status = StructNew()> 
    <cfset myInterest = TotalInterest(Form.Principal, Form.AnnualPercent, 
        Form.Months, status)> 
    <cfoutput>  
        Loan amount: #Form.Principal#<br> 
        Annual percentage rate: #Form.AnnualPercent#<br> 
        Loan duration: #Form.Months# months<br> 
        TOTAL INTEREST: #myInterest#<br> 
    </cfoutput> 
<cfcatch type="InvalidData"> 
    <cfoutput> 
        #cfcatch.message#<br> 
    </cfoutput> 
</cfcatch>

</cftry>