jQuery: No headers on success

I've been working in my spare time on a pure js UI using jQuery. While I love jQuery, the inconsistency of its AJAX callback handlers has been driving me nuts. What I needed was to handle pagination on a collection of resources by putting the pagination meta-data (stuff like which page was being rendered and how many resources per page) in the response headers. And of course, there is no way to get hold of the headers on success() because the success callback gives you only data and status. On the other hand the error handler gives me xmlHttpRequest, textStatus and errorThrown and xmlHttpRequest.getResponseHeader() would give me the response headers. But only if there was an error. Which there wasn't.

After a bunch of googling, the only solution I found looked something like this:
...
var xhr = $.post(form.action, params, function(data) {
if (xhr.getResponseHeader('Stripes-Success')) {
$('#form').hide();
$('#people').html(data);
}
else {
$('#form').html(data);
}
});
...

This forces me to create the handler when invoking an ajax method like $.post or $.get so that I could form a closure around the xhr (XmlHttpRequest) instance returned by them and make it available to my handler. A gross misuse of closures. Also, if I construct my handlers elsewhere (which is the case), I'm all outta luck.

I figure that rather than jumping through hoops or using closures for all the wrong reasons, I'd much rather patch jQuery. The fix turned out to be surprisingly simple and applies to lines 2815 to 2818 in jQuery 1.2.6. I changed
function success(){
// If a local callback was specified, fire it and pass it the data
if ( s.success )
s.success( data, status);
to
function success(){
// If a local callback was specified, fire it and pass it the data
if ( s.success )
s.success( data, status, xhr );

The best part is that no existing code is broken by this change. Now to get hold of the headers in a success handler, here's what you need to do:
function(data, textStatus, xhr) {
Console.debug(xhr.getResponseHeader("Page"));
Console.debug(xhr.getResponseHeader("Per-Page"));
}

4 comments:

Cherry said...

This is something that the community has always rallied around John Resig and team to patch but never got to. As a matter of fact many of them, for their deployments fixed it they way you did. Support.com is one example.

neerbos said...

Why not use the complete callback when using the ajax function? See the jquery docs

This gives you access to the XMLHttpRequest.

BitSucker said...

I agree that exposing the xhr to the success callback looks like a natural enhancement, and I'm not experienced with jquery (long-time prototype user) so I might be missing some stylistic conventions... Anyway, seems worthwhile to point out that your "gross misuse of closures" isn't the only way to expose the xhr returned by the call to the callback: the outer code could alternatively save the object somewhere (as a property on some app object) for the callback to retreive it. I do like your way better, but for those that don't want to patch the lib: other options exist.

Another (perhaps more important?) observation: I'm not certain, but I thought I'd heard that the callback for an xhr could possibly be invoked SYNCHRONOUSLY -- which is to say, in the same call-stack, before the outer invocation returns the xhr object at all (this is certainly the case with many other async APIs, anyway). In that case, passing the object directly to the callback (as in your patch) would be the ONLY way to guarantee its availability... Anyone know the contract for the xhr API?

Sidu said...

@neerbos - I could do, but my compulsive urge for consistency gets in the way :)

@bitsucker - Agreed; without modifying the original source, global variables seem to be the only option. Not a path I'd choose to walk though - modifying the source seemed the lesser evil.
For you're second point - not a clue. I'd like to know too, so do let me know if you find out.