Posted February 18, 2011 at 6:29 PM
Last week, I started to explore Deferred objects and the AJAX updates made to jQuery 1.5 byJulian Aubourg. To kick off the exploration, I created a function that would essentially proxy the XHR request object, normalizing the core response in order to account for web services that use meaningful HTTP status codes in order to define request errors. In the comments to that post, Julian Aubourg himself pointed out that I could use the new $.ajaxPrefilter() method as a means to implement the same functionality in a much more flexible way. To help wrap my head around his comments, I decided to refactor my previous post using a combination of $.ajaxSetup() and $.ajaxPrefilter().
As a quick recap, jQuery only parses "success" responses returned from the server. That means that requests with 40x and 50x HTTP status codes do not get parsed. The problem with this is that web services often return status codes like 400 and 401 in order to provide a better context for the response data. In order to make sure that jQuery parses the JSON data associated with these non-200 responses, we need to proxy the AJAX request and augment the routing logic.
In my previous post, I created this AJAX proxy by manually wrapping the core jqXHR object as part of my AJAX request. As Julian pointed out, however, it would be much better if I could leave this kind of object decoration up to a configuration option. By using his approach, not only would I factor out the burden of creating the proxy object, I would also allow the request normalization to be done either on a global or one-off basis.
To see what I'm talking about, take a look at this demo code. As you read through it, notice that I am using $.ajaxSetup() to apply the normalization and then $.ajaxPrefilter() to implement it.
NOTE: I am not going to reproduce the ColdFusion API code since it is not really relevant to this exploration. If you like, you can alway refer to my previous post.
Launch code in new window » Download code as text file »
"message"
style="display: none ;">At the top of the code, I am using $.ajaxSetup() to indicate that all AJAX requests will be made to a web service that uses targeted HTTP status codes in order to help define its response.
Launch code in new window » Download code as text file »
Really, all this is doing is helping to define a base hash of AJAX configuration options that will be used by AJAX requests on the current page. This can be overridden by each AJAX request - $.ajax(); but for our purposes, we'll stick with the global configuration.
And, once we have the global configuration in place, we use the $.ajaxPrefilter() method to augment the jQuery XHR object (jqXHR) based on the configuration options.
Launch code in new window » Download code as text file »
The function reference passed to the $.ajaxPrefilter() method will be executed for every single AJAX request. In this case, we are looking at the merged options hash (NOTE: localOptions are the options defined directly by the $.ajax() method) and, if the "normalizeForStatusCodes" option is true, we are augmenting the jqXHR object.
The augmentation of the jqXHR object is being encapsulated within the function, normalizeAJAXRequestForStatusCodes(). I am doing this mostly because it helps me think about the problem in isolated, compartmentalized steps. Of course, we aren't just moving code around - we're changing the way it works; and, when we use $.ajaxPrefilter(), we can't return a proxy object in the same way that we were doing before. Rather, we need to directly manipulate the outgoing jqXHR object.
As per Julian's lead, I am doing this by calling the promise() method on my deferred-bridge and passing-in the jqXHR object:
Launch code in new window » Download code as text file »
The name, "promise," is kind of misleading here - we're not really creating a new object; when you pass an object (as an argument) into the promise() method, what you're actually doing is copying the method references from one object to the other. So, in this case, we are copying all of the promise-based methods - then, done, fail, isResolved, isRejected, promise - from the normalizedRequest Deferred object into the jqXHR object.
This is actually super interesting! This works because the object reference never changes - only its properties do. And, since the properties consist of methods that are lexically bound, you're essentially creating an in-place proxy object, leaving the same outer shell intact. There's something deeply satisfying about that.
I'm still wrapping my head around all of the new jQuery 1.5 functionality; but, a huge thanks to Julian for point this stuff out to me. There are, of course, other ways to configure outgoing AJAX requests in jQuery such as the beforeSend callback; but, this combination of $.ajaxSetup() and $.ajaxPrefilter() just feels very clean.