Handling AJAX Errors With jQuery

Posted November 7, 2008 at 8:57 AM

Tags: Javascript / DHTML, AJAX

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 »
6,371 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 »
111 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 »
6,371 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.


Tim
Sep 22, 2009 at 2:54 PM // reply »
6 Comments

@Ben
your above comment "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."

This is exactly the issue I am having. I have an application where the user is logged in they have navigated to a page that has various buttons that load different regions with ajax calls. However, if the user leaves for a long period of time and comes back and clicks one of the buttons that makes an ajax call the Application.cfc is catching it that the cflogin idleTimeout has been exceeded. Since my typical response for a non ajax page request is to log the user out and redirect them to the login screen which works for non-ajax calls. However, when a session has timed out it simply redirects the ajax loaded div to the login screen. So essentially it has a page (header/login/footer) loaded inside the div that they attempted to load dynamically.

@Ben || Terry --- any ideas on how I can intercept / prevent the application cfc from redirecting just the region. How can I make my onRequestStart method aware that this is an AJAX call and simply return a "Your session has timed out" message rather than the normal ajax response which is loading a .cfm page.

Sorry for the long post any help would be appreciated as I've been struggling to find a solution to this issue.

Thanks,
tim


Sep 24, 2009 at 9:28 AM // reply »
6,371 Comments

@Tim,

What I have learned to do lately, is have my AJAX requests run through a slightly different security model. Rather than have them redirect to a login, an AJAX request will check for session and if the user is currently logged-out, it will return a valid AJAX response like this:

{
success: false,
data: "",
errors: [ "You are not currently logged in - refresh your page" ]
}

Right now, the user had to manually refresh the page, but I suppose you could intercept this response in your own way and handle it more gracefully.


Post Comment  |  Ask Ben

Recent Blog Comments
Nov 7, 2009 at 5:53 PM
Ask Ben: Javascript String Replace Method
You can find here an advanced function that prepared with javascript replace function. This can make the first letters of words, sentences, lines and whatever you define automatically: http://www.m ... read »
Andrew Neely
Nov 7, 2009 at 4:56 PM
A Moment That Touched Me - The Fountainhead
Ben, Glad you enjoyed the podcast. Yeah, the Tank Riot guys can get really chatty during the episodes, but that's part of the charm of it for me. They've covered everything from Nichola Tesla to Cha ... read »
Nov 7, 2009 at 4:43 PM
Building A Fixed-Position Bottom Menu Bar (ala FaceBook)
Is it possible to make some more MenĂ¼`s ? ... read »
Jill
Nov 7, 2009 at 11:40 AM
How To Unformat Your Code (Like A Pro)
Derek, I think you might be right - sweet! Thanks for the link :) ... read »
Nov 7, 2009 at 11:25 AM
How To Unformat Your Code (Like A Pro)
I think it would be way easier to just use this http://www.logichammer.com/html-formatter/ He just released v3 and it rocks. ... read »
Jill
Nov 7, 2009 at 7:58 AM
How To Unformat Your Code (Like A Pro)
LMAO - this was pretty funny! I have to admit - I also love to reformat code so I can read it. My boss used to tell me to leave my OCD at home. Now I don't feel so bad after reading everyone else' ... read »
Nov 6, 2009 at 10:10 PM
How To Unformat Your Code (Like A Pro)
The timing of this post is just uncanny. I spent the last 15-20 minutes manually un-formatting my "Ben Nadel" style code within a CFC of mine. I was really digging the readability a few weeks ago, bu ... read »
Roe
Nov 6, 2009 at 5:11 PM
Passing Arrays By Reference In ColdFusion - SWEEET!
ArraySort also reorders the results of these java obj's ... read »