Making multiple AJAX calls and Deciphering $.when.apply($, array);

I had a chance to help a FCC student with his project. The main challenge of the project is about making multiple AJAX calls. The pseudo code for the project look like this:

  1. Make multiple AJAX calls to a server
  2. Store the results in an array
  3. Sort or group the array by status property.
  4. Create DOMs and attach them to HTML page.

The following code was what the student’s code looked like:

var i,
ids=[‘1’,’2',’3',…,’n’],
results = [];
// 1. Make multiple AJAX calls to a Server
for (i=0; i < ids.length; ++i){
$.ajax(‘http://api.server.com/'+id, {
success: function(result) {
// 2. Store the results in an array
results.push(result);
}
});
}
// 3. Process the result array.
groupedResults = groupByStatus(results)
// 4. Create DOMs and attach them to HTML page.
buildDOMAndAttach(groupedResults);

For the sake of the conversation, let’s assume that groupByStatus() and buildDomAndAttach() functions magically do what they are supposed to do.

At a first glance, this code snippet looks innocent and do what we would like to do. However, when we run the code, we will see nothing on the browser. That is because when we group the results and build DOM, the results array is empty.

Why is it empty? Because, $.ajax is asynchronous call. When the JavaScript engine runs the next line of code of $.ajax(…), it didn’t finish fetching data from the server. It simply put that request on a queue somewhere behind curtain and do it whenever it has time to do so. And when it finishes the request, it will tell us that it has finished and the data are ready.

Then how can we tell the JavaScript engine to do something with the result when the the request is done? We can make use of Promise.

Gust pager: when the pager buzzes, please return to the host.

Promise is like a guest pager. When you go get a burger at a burger shop, you place an order and they give you a guest pager. You hold on to the pager and you can go to the next shop and take care of other errant until the page rings. When the page rings, you go back to the burger shop and pick up the foods.

The previous scenario can be written in JavaScript like this:

// 1. Place a order for me!
var pager = placeAnOrder(“http://api.burger.shop/me");
pager.done(function(food){
// 3. When the food is ready, eat!
eat(food);
});
// 2. Take care of other errants
takeCareOfErrant();

Note that the order of the action is not same as the sequence of the code.

Let’s wait for all the pages buzz

But, what if you and your friends placed separate orders, and you and your friends want to eat together when all the orders are ready? That’s when jQuery.when() function comes into play.

jQuery.when() accepts list of promises. So the scenario with multiple orders can be written like this:

var i,
names=[‘me’, ‘friend1’, ‘friend2’],
pager,
pagers=[],
foodTray=[];
// 1. Place orders for each person
for(i = 0; i < names.length; ++i){
pager = placeAnOrder(“http://api.burger.shope" + names[i]);
pager.done(function(food){
// 3. when a order is ready, place the food on the tray
foodTray.push(food);
});
  // 2. Collect the pager in one collection
pagers.push(pager);
}
// 4. when all the orders are ready, eat together!
$.when(pagers[0], pagers[1], pagers[2]).then(function(){
eatTogether(foodTray);
});

The above code will collect all the pagers from the order. When each order is completed, we will put the food on a tray. And $.when() function will ensure that we wait for all the pagers ring before we eat together.

However, there is a little problem. $.when() function accept lists of arguments, so we have to pass them like this:

$.when(pagers[0], pagers[1], pagers[2]);

This works in our case because we only have 3 pagers. But, what if we have hundreds of them? Or what if we don’t know how may at the time of writing code? We need a way to convert this pagers array into list of arguments. That’s where apply() function comes in handy.

fun.apply(thisArgs, array);

will call the function fun() with argument list from the array. And the first argument is the value of ‘this’ keyword inside of fun() function.

When we call $.when() function, ‘this’ keyword inside the function is implicitly bind to jQuery object. But when we call $.when.apply(), we have to explicitly bind ‘this’ keyword to something. And we know that the binding has to be with jQuery object, so we pass jQuery object, a.k.a., $ as the first argument.

So the last line of the previous solution can be rewritten like this:

$.when.apply($, pagers).then(…);

By this point, I hope you figure that what need to be changed to correct the solution. With the knowledge of the promise, $.when() and apply() function, the solution are rewritten like this:

var i,
ids=[‘1’,’2',’3',…,’n’],
results = [],
deferred,
deferreds = [];
// 1. Make multiple AJAX calls to a Server
for (i=0; i < ids.length; ++i){
deferred = $.ajax(‘http://api.server.com/'+id, {
success: function(result) {
// 2. Store the results in an array
results.push(result);
}
deferreds.push(deferred);
});
$.when.apply($, deferreds).then(function() {
// 3. Process the result array.
groupedResults = groupByStatus(results)
// 4. Create DOMs and attach them to HTML page.
buildDOMAndAttach(groupedResults);
});

I hope this helps you understand how to make multiple ajax calls. Please leave a comment for other questions.