Using promises to merge async jQuery Ajax calls

When we use jQuery to call a service it will, by default, process the call asynchronously. This is preferred behavior. We wouldn't want our UI thread waiting for a long-running task. But, this implies that the return value of such an Ajax call -or variables defined inside the function scope of this Ajax call- is only accessible within the call itself. You can never access asynchronous objects from a synchronous context, and vice versa.
A simple -and widely used- workaround for this problem is setting the async flag to false. This way jQuery is forced to wait on the call to finish before proceeding with the next step. You can now access the return value and store it into a global variable, which you can make accessible to other parts of your code. Even though this isn’t a perfect implementation -or even one that I would recommend-, for most cases it will do the job.

But, as of jQuery 1.8, the async tag is deprecated. This forces us to use of the success/error/complete callback options.
Not a bad decision, according to this writer, but one that forces us to rethink the way we implement javascript service calls.

Using callback functions to fetch the return value

The following example demonstrates how to fetch all posts of my blog using a callback function and presenting them as a json objects in the console.log:


//calls the provided url and calls the callback function for its return value
function fetchBlogPosts(feed, callback){
  $.ajax({
    type: 'GET',
    url: document.location.protocol +
         '//ajax.googleapis.com/ajax/services/feed/load?v=1.0&num=1000&callback=?&q=' +
         encodeURIComponent(feed),
    dataType: 'json',
    error: function () {
      alert('Unable to load feed, Incorrect path or invalid feed');
    },
    success: function (xml) {
      callback(xml.responseData.feed.entries);
    }
  });
}

var feed = 'http://www.timsommer.be/blog/feed/';

fetchBlogPosts(feed, logPosts);

function logPosts(posts) {
  console.log(posts);
}

This actually works quite nicely. Once the Ajax call is completed, the success function passes its retrieved object to the callback function. The callback function, when invoked, logs its passed parameter to the console window.

Now lets say we would like to fetch posts from multiple sources. This doesn’t have to mean many changes, we could simply change the feed variable into an array.

Like so:


var feeds = ['http://www.timsommer.be/blog/feed/',
             'http://blog.voltje.be/feed/',
             'http://petermorlion.blogspot.com/feeds/posts/default'];

for(var i = 0; i < feeds.length; i++){
  fetchBlogPosts(feeds[i], logPosts);
}

function logPosts(posts) {
  console.log(posts);
}

We use the for-loop to loop through each feed and invoke the fetchBlogPosts function.

For this example I used the blogs of two colleagues of mine, whose work I deeply respect. Be sure to visit their sites if you haven't already.

This solution is acceptable. But, since the tasks run asynchronously, the values of one feed can never access the other.
But what if we would want to apply sorting? Or search through the values of the feeds? Or simply combine them? At this moment, we can’t..

That’s when promises come in to play.

Promises

For those of you who don’t know promises, it’s basically an easy way to handle an asynchronous call. To explain promises, take a look at the following example (for simplicity I used jQuery in this sample. Please note that promises are part of ECMAScipt 5 and are not restricted to any library. jQuery just builds upon these native promise objects in its own API):


$.when($.ajax('test.aspx')).then(function(data, textStatus, jqXHR){
 alert( jqXHR.status ); // alerts 200
});

composedPromise = $.when(anAsyncFunction(), anotherAsyncFunction());

To put it simply, we start with a function which JavaScript will execute asynchronously. JavaScript promises that when our function finishes, it will then invoke another function, which we provide.
In this sample, we ask JavaScript to fetch 'test.aspx'. When 'test.aspx' is retrieved we then ask JavaScript to invoke our anonymous function. It’s async programming made simple !

In the second sample you see that promises can also be grouped into one composed object, instructing JavaScript to wait for each asynchronous task to finish.

Promises are a whole chapter on itself and I will not go into detail here. But if you want to know more you can easily find a lot of resources on the web. Here, here and here are good places to start.

Using promises to merge async return values

Now, if we could use promises in our example, and make JavaScript promise to invoke a function when all calls are finished, we would be home free.
And that’s exactly what we are going to do:


// calls the provided url and returns a reference to the Ajax call
function fetchBlogPosts(feed) {
  return $.ajax({
    type: 'GET',
    url: document.location.protocol +
         '//ajax.googleapis.com/ajax/services/feed/load?v=1.0&num=1000&callback=?&q=' +
         encodeURIComponent(feed),
    dataType: 'json',
    error: function () {
      alert('Unable to load feed, Incorrect path or invalid feed');
    },
    success: function (xml) {}
  });
}

var results = [];
var feeds = ['http://www.timsommer.be/blog/feed/',
             'http://blog.voltje.be/feed/',
             'http://petermorlion.blogspot.com/feeds/posts/default/'];

// store the reference to the Ajax feed for later usage
for (f in feeds) {
  results.push(fetchBlogPosts(feeds[f]));
}

// invoke each function stored in the result array and proceed when they are all done
$.when.apply(this, results).done(function () {

  var blogPosts = [];

  // fetch the result from each arg
  // each arg contains the result of one succes ajax function.
  // in this case, three arguments exist, one for each ajax call.
  for (var i = 0; i < arguments.length; i++) {

    // retrieve the entries from the argument parameter
    values = arguments[i][0].responseData.feed.entries;
    console.log(values);

    // push the retrieved value to a global function variable, which we can.
    // this function is processed synchronously, when all ajax calls have been completed
    blogPosts.push(values);

  };

  var merged = [];

  // merge the arrays
  for (var i = 0; i < blogPosts.length; i++) {
    $.merge(merged, blogPosts[i]);
  };

  // and log them to the console
  console.log(merged);
});

Let’s take a quick look.

We notice a first change in the fetchBlogPosts function, which now returns the $.ajax instead of invoking it. This is because we are going to use the apply prototype function to invoke the fetchBlogPosts function. When we use the apply function with an array, all the values of that array are passed into that function as arguments. If the array has 5 values, the function will be invoked with 5 arguments. But since I don’t really like using arguments -I want my function to use only the parameters I define- returning the Ajax handler will allow me to avoid using them.

You may also notice that we don’t use a callback function anymore. Since we are using promises, we don’t need it anymore. When a (jQuery) promise succeeds, the values returned to the success function can be retrieved using the 'done' promise function.

The next change you notice is the result array, which is filled with references to the fetchBlogPosts function. We will use this later on in our apply prototype function implementation.

And then we see the promise itself. Let's see if we can put it into words like we did before. When each Ajax handler stored in the result array (think of the apply function as the for-loop we used before) is done with its call, invoke the following anonymous function. In that case we loop through each argument (there should be three, one for each completed ajax call) and retrieve the blog posts. Since we are now in the same function scope, we can use a global function variable to merge the posts and log them to the console. You could just as easily return them to perform filtering, sorting, etc.

The important thing is that we now have a merged list of awesome blogposts!

You might enjoy


Quick! How much time will it take to fly around the world? How much cloud storage would it take…

I recentely started doing all my development work in Virtual Machines. I have one optimized for web-development, another for…

Tim Sommer

I'm a Web Developer with +7 years of experience in the .Net framework, with a focus on HTML5 and JavaScript.

Belgium, Antwerp
comments powered by Disqus
OWIC
Member of The Internet Defense League