Handling AJAX Errors With jQuery

Posted November 7, 2008 at 8:57 AM

Tags: AJAX, Javascript / DHTML

jQuery is the most awesome javascript library that exists. Every day, I'm finding new ways to leverage it and shorter, more efficient ways to get things done. But, while most things are easy to do, the solution is not always immediately evident. One of the things that took me a good while to figure out was how to gracefully handle AJAX errors. Anyone who's worked with JSON requests and other AJAX calls knows that sometimes, that stuff just fails silently; you know something went wrong, but no errors were thrown. If it wasn't for FireBug showing us 404 or 500 style errors, there'd be no evidence at all of these fails.

 
 
 
 
 
 
 
 
 
 

I've come up with a way to centralize my AJAX calls in a way that seemlessly handles all errors that occur either from the request connection or the JSON processing (ie. poorly formed JSON that cannot be converted back into Javascript data types). I'm not sure if this is the best of all ways, but I'm liking it. The whole concept rests on the fact that all of my system API (AJAX) calls return a uniform response with the following structure:

 Launch code in new window » Download code as text file »

  • {
  • SUCCESS: true,
  • DATA: "",
  • ERRORS: []
  • }

The Success property flags the request as having executed properly and returned the expected data. The Data property can be anything it needs to be. The Errors property is an array of any errors that need to be reported. It is only by requiring that all AJAX requests expect this that I can easily handle all errors.

In production, the following code would probably be part of some other object or integrated into the Javascript framework in a different way, but for this demo, I'm going to break out my AJAX request pipeline into its own class:

 Launch code in new window » Download code as text file »

  • // Create an object to handle our AJAX.
  • function AJAX(){
  • var objSelf = this;
  •  
  • // This struct will cache the current XmlHTTP requests
  • // so that we can reference them if a call fails.
  • this.CurrentRequests = {};
  • }
  •  
  •  
  • // This handles the JSON request. This checks to see if the current
  • // request is already being processed and also handles any error
  • // wiring that is required.
  • AJAX.prototype.GetJSON = function( $1, $2, $3, $4 ){
  • var objSelf = this;
  • var strName = $1;
  • var strURL = $2;
  • var objOptions = $3;
  • var fnCallback = $4;
  •  
  • // Check to see if there are only three arguments. If there
  • // are only 3, then the first one (name of request) which is
  • // optional was not passed in. Shift the other arguments
  • // to the appropriate variables.
  • if (arguments.length == 3){
  •  
  • // Name is not being used.
  • strName = null;
  • strURL = $1;
  • objOptions = $2;
  • fnCallback = $3;
  •  
  • }
  •  
  • // First, we have to check to see if this request is
  • // already being processed. We don't want the user to
  • // try and fire off multiple requests of the same type.
  • // Of course, if the name is NULL, then don't worry.
  • if (!strName || !this.CurrentRequests[ strName ]){
  •  
  • // Store current request.
  • this.CurrentRequests[ strName ] = true;
  •  
  • // Make actual AJAX request.
  • $.ajax(
  • {
  • // Basic JSON properties.
  • url: strURL,
  • data: objOptions,
  • dataType: "json",
  •  
  • // The success call back.
  • success: function( objResponse ){
  • // Remove request flag.
  • objSelf.CurrentRequests[ strName ] = false;
  •  
  • // Pass off to success handler.
  • fnCallback( objResponse );
  • },
  •  
  • // The error handler.
  • error: function( objRequest ){
  • // Remove request flag.
  • objSelf.CurrentRequests[ strName ] = false;
  •  
  • // Pass off to fail handler.
  • objSelf.AJAXFailHandler(
  • objRequest,
  • fnCallback
  • );
  • }
  • }
  • );
  •  
  • } else {
  •  
  • // This request is currently being processed.
  • alert( "Request being processed. Be patient." );
  •  
  • }
  • }
  •  
  •  
  • // This will handle all AJAX failures.
  • AJAX.prototype.AJAXFailHandler = function( objRequest, fnCallback ){
  • // Since this AJAX request failed, let's call the callback
  • // but manually create a failure response.
  • fnCallback(
  • {
  • SUCCESS: false,
  • DATA: "",
  • ERRORS: [ "Request failed" ]
  • }
  • );
  • }

(I'm sorry the color coding doesn't work for my Javascript files) There's not a whole lot going on here, but let's walk through it. First off, one thing you can do here is make sure that only one AJAX request (of a particular type) can be processed at a time. The GetJSON() method here can take 3 or 4 arguments. If you pass in the first, optional argument - the name of the request - the GetJSON() logic will make sure that it does not launch multiple instances of the same type of AJAX request at any one time. If you pass in only the three required fields, the GetJSON() method will allow parallel AJAX requests of the same type. You will see this in the demo below - I serialize my 200 requests but allow my 404 requests to happen in parallel.

The methodology that I use leverages the $.ajax() jQuery method. I used to just use the $.getJSON() method of the jQuery library, but the $.ajax() method gives us access to the Error call back method of the AJAX request. With this method and my unified AJAX response, handling errors is actually quite easy. All AJAX errors are piped through my AJAXFailHandler() method which creates a "fail" AJAX response (sets SUCCESS flag to false) and then manually executes the AJAX callback, passing in the fail response. This way, from the AJAX response handler's point of view, it has no idea that anything has gone wrong - it only knows that it received a response object that was either flagged as a success or a failure.

Now, let's take a look at the demo page:

 Launch code in new window » Download code as text file »

  • <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  • <html>
  • <head>
  • <title>Handling AJAX Errors With jQuery</title>
  • <script type="text/javascript" src="jquery-1.2.6.min.js"></script>
  • <script type="text/javascript" src="script.js"></script>
  • <script type="text/javascript">
  •  
  • // Initialize document.
  • $(
  • function(){
  • var objAJAX = new AJAX();
  •  
  • // Get reference to the three links.
  • var j404 = $( "#error-404" );
  • var jNoError = $( "#no-error" );
  •  
  • // Set up 404 link.
  • j404
  • .attr( "href", "javascript:void(0)" )
  • .click(
  • function( objEvent ){
  • // Make AJAX request.
  • objAJAX.GetJSON(
  • "does-not-exist.cfm",
  • {},
  • Do404RequestHandler
  • );
  •  
  • // Prevent default.
  • objEvent.preventDefault();
  • return( false );
  • }
  • )
  • ;
  •  
  • // Set up no-error link.
  • jNoError
  • .attr( "href", "javascript:void(0)" )
  • .click(
  • function( objEvent ){
  • // Make AJAX request.
  • objAJAX.GetJSON(
  • "NoErrorRequest",
  • "200.cfm",
  • {},
  • NoErrorRequestHandler
  • );
  •  
  • // Prevent default.
  • objEvent.preventDefault();
  • return( false );
  • }
  • )
  • ;
  • }
  • );
  •  
  •  
  • // I handle the 404 request repsonse.
  • function Do404RequestHandler( objResponse ){
  • // Check to see if request was successful.
  • if (objResponse.SUCCESS){
  •  
  • alert( "Success!" );
  •  
  • } else {
  •  
  • alert( "404 Error!" );
  • }
  • }
  •  
  •  
  • // I handle the no-error request repsonse.
  • function NoErrorRequestHandler( objResponse ){
  • // Check to see if request was successful.
  • if (objResponse.SUCCESS){
  •  
  • alert( "Success!" );
  •  
  • } else {
  •  
  • alert( "No-Error Error!" );
  • }
  • }
  •  
  • </script>
  • </head>
  • <body>
  •  
  • <h1>
  • Handling AJAX Errors With jQuery
  • </h1>
  •  
  • <p>
  • <a id="error-404">404 Error</a> &nbsp;|&nbsp;
  • <a id="no-error">Success</a>
  • </p>
  •  
  • </body>
  • </html>

As you can see above, we are using jQuery to hook the links up to launch AJAX calls. Each of the two links - 404 and 200 responses - has its own response handler method. These methods, check to see if the response object was successful and just alerts the user. Notice that only the 200 style request passes in the name of the request, "NoErrorRequest"; this will ensure that the 200 style requests are serialized. The 404 style request, on the other hand, does not label its AJAX requests and therefore can make as many parallel requests as it likes.

I'm sure that I will continue to evolve the way I handle these situations over time, but so far, I have been really pleased with this methodology. It completely differentiates the two types of AJAX errors - logical vs. critical - and moves all critical error handling out of the business logic of the application.

If you are curious to see what is happening at the other end of the 200.cfm request, here is that template:

 Launch code in new window » Download code as text file »

  • <!--- Create the response. --->
  • <cfset objResponse = {
  • Success = true,
  • Data = "Good request",
  • Errors = []
  • } />
  •  
  • <!--- Serialize the response. --->
  • <cfset strJSON = SerializeJSON( objResponse ) />
  •  
  • <!--- Get the binary response. --->
  • <cfset binJSON = ToBinary( ToBase64( strJSON ) ) />
  •  
  • <!--- Stream it back. --->
  • <cfheader
  • name="content-length"
  • value="#ArrayLen( binJSON )#"
  • />
  •  
  • <cfcontent
  • type="text/json"
  • variable="#binJSON#"
  • />

As you can see, it simply creates my unified AJAX response object and streams it back to the client.

Download Code Snippet ZIP File

Post Comment  |  Ask Ben  |  Permalink  |  Other Searches  |  Print Page




Learning ColdFusion 9 - ColdFusion 9 tutorials, samples, examples, demos

Reader Comments

Nov 7, 2008 at 11:12 AM // reply »
6 Comments

I have used this concept with Prototype and it works great. You can take this one step further and throw/capture a 403 status code.
Many folks totally forget about authentication when using ajax, but it's really pretty easy.
If authentication fails for an ajax request, then just have coldfusion set a 403. Capture the 403 in your javascript and then handle as you see fit.


Nov 7, 2008 at 2:22 PM // reply »
5,406 Comments

@Terry,

That's interesting - I never thought about an authorization issue. I just always assumed that if the main page (client) was not logged in, AJAX wouldn't even be a question. I guess you could get to a point where the session times out, but the client is still open... I wonder how I would address something like this.


Nov 7, 2008 at 3:48 PM // reply »
92 Comments

Also, for those who just want a way to handle an error to an AJAX operation, jQuery has some global event handlers for handling all AJAX operations:

http://docs.jquery.com/Ajax/ajaxError#callback

$("#msg").ajaxError(function(event, request, settings){
$(this).append("<li>Error requesting page " + settings.url + "</li>");
});

<ul id="msg"></ul>

In the above example, anytime an error would occur with an AJAX call a new bullet would be added to the ul#msg element.


Nov 7, 2008 at 3:56 PM // reply »
5,406 Comments

@Dan,

When I first started playing with this stuff, I tried that. I actually attached AJAX errors to the BODY tag (I really just chose it arbitrarily since there is only one BODY tag).

What I liked about the $.ajax() method is that I could create more complex success / error handlers. I am sure it could be done other ways, but this is how it occurred to me.


Post Comment  |  Ask Ben

Recent Blog Comments
Jason Fisher
Jul 4, 2009 at 4:35 PM
Adobe Announces That HomeSite Is Officially Dead
I'm with Mark and Tim: still the best IDE for CF, especially for those of us with years of customization which can so easily be moved from machine to machine. I just have trouble making the Eclipse ... read »
Secret Admirer
Jul 4, 2009 at 12:23 PM
Project HUGE: Huge In A Hurry - Get Big - Phase 2 / Week 3
My Poor Dreamboat :( I feel so sad when I know you are hurting. I hope you feel better soon. ... read »
Jul 4, 2009 at 9:42 AM
FLV 404 Error On Windows 2003 Server
I bookmarked this page. Thanks for given this great post.... ... read »
Jul 4, 2009 at 4:00 AM
Terms Of Service / Privacy Policy Document Generator
thanks ben, I'm not a big fan of contracts so to find your no no-nesense ToS generator has helped me no end. all the best matt ... read »
Justice
Jul 3, 2009 at 11:10 PM
Create A Running Average Without Storing Individual Values
@Ben, I think you're going about this the wrong way. You're trying to use complicated techniques when there is a simple and beautiful technique readily available (a la Gary Funk's comment). Instead ... read »
Bob
Jul 3, 2009 at 9:19 PM
Project HUGE: Huge In A Hurry - Get Big - Phase 3 / Week 1
a good technical explanation http://crossfitphoenix.typepad.com/crossfit_phoenix_forging_/the-overhead-squat.html ... read »
Jul 3, 2009 at 9:03 PM
Create A Running Average Without Storing Individual Values
If I wanted to do this and only carry two numbers, I'd keep track of the sum and N. Then you are pretty much accurate all the time. average = (sum + new_number) / (N + 1) But all this was in a for ... read »
Roland Collins
Jul 3, 2009 at 8:58 PM
Create A Running Average Without Storing Individual Values
@Martin - not just floating point though. Depending on what langauge you're working in, decimals can cause just as many headaches if they're not precise enough. But again, for most applications, th ... read »