Error handling is one of the most often overlooked parts of application development. How often have you been to a web page and seen a database error, JavaScript error, connection error, or page not found error? These are examples of errors that are not handled properly. It is up to you to decide how you want to recover from a particular error, but I will show you where the errors can occur in a Flash Remoting application and give you some basic strategies for developing your own error handling.
Errors can occur in any aspect of your Flash movie or in the server-side code of a Flash Remoting application. Errors can be broken down into these basic types:
Syntax errors are caused by malformed code (you typed something incorrectly, such as a misplaced comma or quote). If the syntax error is in the client-side code, Flash's Output window generally displays the error when you attempt to compile your .swf file. Consult the online help, Chapter 15, or ActionScript for Flash MX: The Definitive Guide for the correct syntax for a given command. Although author-time syntax errors are usually easy to find and fix, those that occur because of user input are more difficult to detect. For example, a user entering a single quote into a form field might cause a syntax error in a SQL statement. Server-side syntax errors should be identified by the server-side development environment, although the exact process varies depending on the server technology in use.
Application errors are general errors in logic. Although the syntax is correct, the code doesn't perform the desired actions accurately. These errors are generally more difficult to detect because they might occur under only specific conditions. These include errors like trying to access an element past the end of an array (such as when using an incorrect index variable). Another common error is using data of the wrong datatype (or trying to perform an invalid operation on a given datatype). For example, if you are trying to add values obtained from user input or XML, which are always strings, you must first convert them to numbers. Flash Remoting can handle native ActionScript datatypes, which reduces this type of error.
Errors at the database level can be caused by bad data, bad datatypes, bad user input, or any combination of things that affect your database. For example, a record may be locked because another user is sorting the database. Or a database field may contain invalid data that confuses your application. You can and should validate the data in your ActionScript code whenever possible. For example, you should check whether the entry in a field is empty or out of range before submitting it to the server (although the server-side database may also validate the data). You can minimize potential problems by writing test routines that examine every record in a database for potential errors before deploying your application.
Errors can occur when reading to or writing from the hard disk if the permission of a file or folder is not set correctly, or if a file is in use. Another common error is running out of disk space (or, for example, exceeding the allotted space for a local shared object).
Errors in connecting to the Flash movie can occur for many reasons, such as name lookup problems with a DNS server, too many connections on the server, or a misspelled page name inside a web page, database, or Flash movie.
Although the errors themselves can't be eliminated, by adding validation code inside your Flash movie you can recover from the inevitable errors gracefully. Many errors are caused by users using your application in ways that you hadn't anticipated. Perform extensive beta testing and add code to prevent or gracefully handle the errors you discover. An ounce of prevention is worth a pound of cure when it comes to user input. For example, restricting the length and allowed characters in a user input field can eliminate most errors and make it simpler to handle those that occur.
External data, whether coming from user input or a remote server, is a common source of error. That said, server errors are handled quite nicely by Flash Remoting and most of them can be trapped easily inside of your Flash movie.
An error that is detected is said to be trapped. Once an error is trapped, you usually need to handle it in some useful way. For example, if the server is inaccessible, you might automatically try again in several seconds, or you might display a message explaining the problem to the user. Server errors are passed to the Flash movie in the serialized onStatus event of a remote call, which triggers an onStatus event in the Flash movie, which in turn is handled by the onStatus( ) method of the responder object or the methodName_Status( ) function, as described earlier. A status object, which you can use to understand and handle the error, is passed to the error handler (either onStatus( ) or methodName_Status( )). In previous examples, I displayed the description property of the status object, which contains a human-readable error message, in the Output window or a text field. The complete properties of the status object are shown in Table 4-1.
Property |
Contents |
---|---|
code |
Text indicating where the error occurred (usually the literal string "SERVER.PROCESSING") |
level |
The literal string "error" |
description |
Human-readable description of the error message |
details |
A stack trace of the error from the server |
type |
The class name of the error |
rootcause |
Another error object that has additional information about the cause of the error (available only if a Java servletException is thrown) |
Most errors in services should be captured at the server level. That is, you can write your CFML, C#, Java, or other server-side code to detect certain types of errors. After capturing the error on the server, you must decide how to handle the particular error:
Return an object to the Flash movie to be dealt with in the onResult( ) method. While this might seem appealing, the Flash movie might not have sufficient knowledge of or control over the server to handle the error effectively.
Throw a controlled error on the server to pass a code back to the onStatus( ) method. For example, errors can be indicated by numeric codes, human-readable text, or both. In general, numeric error codes are easiest to deal with programmatically, but human-readable text is most useful for providing information to the user. A controlled error is one in which the server returns a more limited or informative error message than is generated by the original error. We explore this option in more detail later in this chapter and in Section 6.5.
Handle the error completely on the server. While this might seem tempting, a one-size-fits-all approach unnecessarily restricts the flexibility of clients that connect to the server. For example, if the server code waits indefinitely for a locked database record to become available, the client-side code won't know what's happening and has no choice but to wait indefinitely for a response. It is often preferable for the server to trap the error, notify the client, and let the client decide how best to proceed. See Section 6.5.1 for an example.
Of course, you can leave the error unhandled or even untrapped on the server, which would leave the error trapping and handling up to the Flash movie entirely. Abdicating control over errors that can and should be trapped or handled at the server level is a poor approach because it leads to errors that can't be prevented or detected on the client side.
Of the various options, the most appealing is to trap the error on the server and send a controlled response to the client where specific and appropriate action can be taken. This approach keeps your error-trapping code on the server separate from your responder code on the client. Typically, you would have a try/catch block on the server to capture the error and then use a throw statement to create your own error message to pass back to Flash. This way, you can control how the error appears to the Flash movie. The ColdFusion code in Example 4-4 demonstrates.
<cfcomponent> <cffunction name="myFunction" access="remote" returntype="string"> <cfargument name="myArgument" type="string" required="true"> <cftry> <cfquery name="myQuery" datasource="northwind"> SELECT * FROM Customers WHERE CustomerID = '#myArgument#' </cfquery> <cfcatch type="database"> <cfthrow message="Database error"> </cfcatch> <cfcatch type="any"> <cfthrow message="Other error"> </cfcatch> </cftry> <cfif myQuery.recordcount EQ 0> <cfthrow message="User not defined"> </cfif> <cfreturn myQuery> </cffunction> </cfcomponent>
The corresponding ActionScript code is shown in Example 4-5.
#include "NetServices.as" var my_conn; // Connection object var my_service; // Service object // Responder for general service methods var Responder = new Object( ); var myURL = "http://localhost/flashservices/gateway" // Capture connection errors and attempt connection up to 5 times Responder.onResult = function (myResults) { trace("No errors"); // put responder code here }; // General error-handling routing Responder.onStatus = function (theError) { switch (theError.description) { case("HTTP: Failed"): trace("Connection error."); // In actual practice, send the user to a general error page break; case("Service threw an exception during method invocation: Database error"): trace("There was a database error"); break; case("Service threw an exception during method invocation: Other error"): trace("There was an undefined error"); break; case("Service threw an exception during method invocation: User not defined"): trace("The user was not found in the database"); break; default: trace("There was a general error"); } trace(theError.description); }; // General connection errors or other errors use the Responder object as well System.onStatus = Responder.onStatus; function init( ) { NetServices.setDefaultGatewayUrl(myURL); my_conn = NetServices.createGatewayConnection( ); myService = my_conn.getService("com.oreilly.frdg.testError"); } // Start init( ); myService.myFunction(Responder,"test");
As you can see from the onStatus( ) function, the error messages returned by the .cfc method are trapped in the switch statement. You should handle the errors as you see fit, such as by redirecting the user to a general error page where you display information about the error.