Skip to main content
Ben Nadel at the jQuery Conference 2010 (Boston, MA) with: Ralph Whitbeck
Ben Nadel at the jQuery Conference 2010 (Boston, MA) with: Ralph Whitbeck

Handling AJAX Errors With jQuery

By
Published in , Comments (14)

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:

{
	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:

// 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:

<!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:

<!--- 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.

Want to use code from this post? Check out the license.

Reader Comments

7 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.

15,848 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.

198 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.

15,848 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.

9 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

15,848 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.

6 Comments

I don't really see the point in returning the success and errors values since that is already contained in the response headers.
Why don't you just respond with the appropriate response code and let the UI deal with it.
This is (imho) much cleaner and much more confirm the REST standard

jQuery returns the status code so you would be able to check this.
eg:

..
if (statusCode === 401) { alert("You are not currently logged in - refresh your page."); }
else if (statusCode === 404) {alert("The requested URL does not exist anymore.");}
else if (statusCode === 500) {alert("");}
etc...

15,848 Comments

@Simon,

Handling logged-in status might make sense to use at the status-code level. However, I think there are many errors that can be returned that are not status-code worthy. For example, if someone were to submit form data that was not completely valid, would that be handled in the status code? And if so, which one would most properly define it?

I think for things of that nature, the request is actually successful, but there intent failed. One I think is part of the HTTP protocol, one is not really part of that purview.

6 Comments

I would guess that a 400 would do in this case:
According to the spec:
400 Bad Request
The request could not be understood by the server due to malformed syntax. The client SHOULD NOT repeat the request without modifications.

And if need be, you can always send a response message along with the status code as well ;)

15,848 Comments

@Simon,

I have to admit, what you're talking about is very intriguing; I have never thought about using status codes to this kind of extent. Let me throw a bad request at a SOAP web service on the ColdFusion server and see what it returns as far as status codes... be right back.

15,848 Comments

@Simon,

Very interesting. I thew a bad request at out the SOAP web service and returns a valid SOAP response, but it *does* send back a 500 error:

"500 Internal Server Error"

Now, 400 vs. 500, I'm not so concerned with - that's just a matter of interpretation; but, the idea of returning *some* sort of non-200 status code, even at the request processing error is something that is a bit new to my frame of mind.

Thank you Simon, you've given me a lot to think about.

6 Comments

:)

If you have full control over the backend which generates the response you should be able to send an approperiate response code.

For a full list you can check the w3 page @ http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html

However, if you are using AJAX a lot, it might be tricky to get a uniform way of handling bad requests and generating proper error messages. This in mind, I still think this is a better way of handling things then returning status and error messages inside the response body.

15,848 Comments

@Simon,

I am not concerned about setting the response headers on the server - ColdFusion makes that a piece of cake. Also, I have a standard web service methodology that streamlines the response, so that they can be handled uniformly.

My only concern would be that setting the status code would trigger the Ajax error callbacks on the jQuery side. This means that the same callback will be executed for timeouts, 400, 404, and 500 errors. I am not sure if that is what will be easier; I only want to be philosophically more strict *if* it also makes life easier.

I'll need some time to think about this. Perhaps I could update the AJAX method in jQuery (extend it) to have a different callback for various status codes. For example:

$.ajax({
on400: function(){ .. },
on500: function(){ .. },
error: function(){ .. }
});

Not sure if that would make things easier or harder... like I said, you've given me a lot to think about.

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel