Nested success callbacks are messy business. The jQuery Promise interface provides a simple and elegant solution.
So, you have to make an AJAX call, and then do something with the return data. No problem, right? Right. What if you have to make two AJAX calls, and then do something with the return data for each one? No problem, right? Right. What if the second AJAX call depends on the success of the first AJAX call?
“…um, well…. then, well… wait a minute… ok, I’ve got it:
I’ll make the first AJAX call…and then inside the success callback function, cache the data somewhere… um… in a global variable for now I guess… yeah, no one will notice. OK, and then inside of that success callback, we’ll nest another AJAX call, and then inside of the second AJAX calls’ success callback, we’ll get our first chunk of data from the global variable, and then, and then… um…and then…”
…yikes!
Obviously, the preceding diatribe was a little over the top, but c’mon…. haven’t you at least once stood at the edge of that cliff and looked over? Fortunately, jQuery features a way out of this mess that is safe, reliable and packed with vitamins.
The jqXHR object
Since version 1.5.1, jqXHR objects returned by $.ajax() implement a “Promise” interface. In short, this means that a number of methods that are exposed provide powerful abstraction for handling multiple asynchronous tasks.
I think it is important to note that the subject of the jQuery Deferred and Promise Objects is deep. It is not an easy topic to jump into and a detailed discussion of this is outside of the scope of this article. What I hope to accomplish here is to provide a simple and fast introduction to the topic, by using a real-world problem / solution.
There are certainly multiple scenarios in which the jQuery Deferred and Promise objects can be useful. I have chosen to use AJAX calls for this, since it is an example of asynchronous behavior that I think most folks can relate to. In order to dramatize the effect of an asynchronous AJAX call, I am using a PHP file that can return a delayed response. Here is the code for the server page:
|
<?php if( isset($_GET["sleep"]) ){ sleep ( $_GET["sleep"] ); } header("Status: 200"); date_default_timezone_set('EST'); echo "<p>Hello, this is the response from the server.<br/>"; echo "This response was sent on " . date('l F jS Y') . " at " . date('h:i:s A') . " <i>(new york city time).<i></p>"; ?> |
OK, now some AJAX.
(Note: I highly recommend that you open up the “network” panel in Firebug or WebKit developer tools so that you can see these AJAX calls in action when viewing the working examples… especially AJAX call # 1, which will take 5 seconds to complete)
Example # 1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
var success = function(d){ $('#target').append(d); }; $().ready(function(){ $('#clear').click(function(){ $('#target').html(''); }); $('#request1').click(function(){ $.get('response.php?sleep=5',success); }); $('#request2').click(function(){ $.get('response.php',success); }); }); |
In Example # 1 we have a fairly straightforward setup: Two buttons, each resulting in an AJAX call. Notice that the 1st AJAX call takes 5 seconds to complete. Instead of providing an inline anonymous function for the success callback of each AJAX call, we have a generic handler named “success”.
But what if we want AJAX call # 2 to depend on AJAX call # 1? That is, we don’t want AJAX call # 2 to even be possible until AJAX call # 1 has completed successfully.
Example # 2
|
$("#request1").click(function() { $.get("response.php", function(d1) { $("#request2").click(function() { $.get("response.php",function(d2) { //...this is not going anyplace good }); }); }); }); |
In Example # 2, we have a pattern that is known as the “Pyramid of Doom”. This might work for the short term, but it is impractical, not at all flexible, and just plain messy. What if there was a third button, and a fourth button… I think you see what I’m getting at here.
Example # 3
|
var aj1 = null, aj2 = null; $('#request1').click(function(){ aj1 = $.get('response.php?sleep=5'); }); $('#request2').click(function(){ aj2 = $.get('response.php'); $.when(aj1,aj2).done(function(a,b){ success(a[0]); success(b[0]); }); }); |
Ahhh… that’s much better!
In Example # 3, we have taken advantage of the Promise interface that is exposed as part of the jqXHR object. If you are wondering what the jqXHR object is, it is the return value of any $.ajax() call. So, while you can simply do this: $.ajax(URL,CALLBACK), the call itself returns a value. That return value is the jqXHR object, which is a superset of the XMLHTTPRequest object.
The return value of $.ajax() is the jqXHR object, which is a superset of the XMLHTTPRequest object, and packed with some powerful features.
We don’t have to dive too deeply into the good ol’ XMLHTTPRequest object (although it is important to know what it is and the critical role that it plays in AJAX). But the key point here is that the jqXHR object wraps the XMLHTTPRequest object, providing some useful features. One of those features is the “Promise” interface.
“….ok, ok Kevin, you’re givin’ me a migraine… where are you going with all this?”
Hang in there; we are at the 99 yard line here. What this all means is that instead of just making an AJAX call, you can assign that AJAX call to a variable. Since the $.ajax() method returns the jqXHR object, your variable is now a Swiss Army knife of AJAXian goodness. You can then implement methods of your object (i.e. your variable). Two of those methods are: .when() and .done();
So, in Example # 3, when we fired the first AJAX call, we assigned its return value to the variable “aj1”. Notice that we did NOT make a success handler call. We simply make the AJAX call, and that call’s return value is held by the variable “aj1”. Then when the user clicks the second button. we set the return value of the second AJAX call to the variable: “aj2”. Now we have two variables and each is a jqXHR object. These are powerful little objects!
The line where the fun really starts is: $.when(aj1,aj2).done()
Notice how the .done() function takes a callback. The callback is fired when the two arguments passed to the .when() method are resolved. And guess what those two arguments are… our two little jqXHR objects!
So, the .done() method knows how to get ahold of the data returned by each of those calls. Now we simply call our generic success handler, passing in the return data of each AJAX call (i.e. each jqXHR object). You may be wondering why I pass in the values “a[0]” and “b[0]”. This is because “a” and “b” are jqXHR objects. It just so happens that the first property of these objects (i.e. the property with the index of 0) happens to be the “responseText” property of that object (i.e. the text sent back from the server).
Phew!
I know that was a lot. But there is much to shout about when it comes to the jQuery Promise interface. As I mentioned, it is a big topic and not easily summarized. But my hope is that these examples will have put a little context around the subject, and will help you to dive in and get started.
Below is the source code for this article’s full working example:
Example # 4:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
|
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>jQuery When - Example # 2</title> <script src="http://code.jquery.com/jquery-1.9.1.min.js"></script> </head> <style> /* CSS removed forbrevity */ </style> <body> <button id="request1">Send 1st AJAX Request</button> <button id="request2">Send 2nd AJAX Request</button> <button id="clear">Clear Page</button> <div id="target"></div> <script> //our success handler for all AJAX calls var success = function(d){ //d is the text returned from the server $('#target').append(d); }, //we'll need these later aj1 = null, aj2 = null; $().ready(function(){ //re-sets the page $('#clear').click(function(){$('#target').html('')}); //when the user kicks off the first ajax call... $('#request1').click(function(){ //set the ajax loader image $('#target').html('<img class="loader" src="ajax-loader.gif" />'); //setup the deferred object that represents this AJAX call //note the query string in the URL: the server will take 5 seconds to respond aj1 = $.get('response.php?sleep=5',function(){ //just a quick anonymous success handler to keep the user informed $('#target').html('<p>ok, the 1st AJAX call is done. Now make the 2nd Ajax Call</p>'); }); }); //when the user kicks off the second ajax call... $('#request2').click(function(){ //if the first ajax call has not run, then warn the user and exit if(!aj1){ $('#target').html('<p>Please make the 1st AJAX call <u>before</u> you make the 2nd AJAX call.</p>'); return; }; //setup the deferred object that represents this AJAX call aj2 = $.get('response.php'); //setup the actions to be taken when BOTH AJAX calls have successfully completed $.when(aj1,aj2).done(function(a,b){ $('#target').html(''); //inside of this function, a & b represent //the response objects for each AJAX call respectively //call our generic success handler and pass it the return text, once for each AJAX call success(a[0]); success(b[0]); }); }); }); </script> </body> </html> |
Summary
In this article we learned about the jQuery Promise interface. We discovered that since version 1.5.1, calls to $.ajax() return a jqXHR object, which implements the Promise interface. We utilized the .when() and .done() methods of this interface to execute callback functions for two asynchronous tasks. This eliminates the need for nested success callback functions.
Helpful Links for the jQuery Deferred, jQuery Promise and jqXHR Objects
jQuery Deferred
http://api.jquery.com/category/deferred-object/
http://api.jquery.com/jQuery.Deferred/
jQuery Promise
http://api.jquery.com/promise/
http://api.jquery.com/deferred.promise/
http://joseoncode.com/2011/09/26/a-walkthrough-jquery-deferred-and-promise/
jQuery jqXHR
http://api.jquery.com/Types/#jqXHR
http://api.jquery.com/jQuery.ajax/#jqXHR