Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pass in an array of Deferreds to $.when()

Here's an contrived example of what's going on: http://jsfiddle.net/adamjford/YNGcm/20/

HTML:

<a href="#">Click me!</a> <div></div> 

JavaScript:

function getSomeDeferredStuff() {     var deferreds = [];      var i = 1;     for (i = 1; i <= 10; i++) {         var count = i;          deferreds.push(         $.post('/echo/html/', {             html: "<p>Task #" + count + " complete.",             delay: count         }).success(function(data) {             $("div").append(data);         }));     }      return deferreds; }  $(function() {     $("a").click(function() {         var deferreds = getSomeDeferredStuff();          $.when(deferreds).done(function() {             $("div").append("<p>All done!</p>");         });     }); }); 

I want "All done!" to appear after all of the deferred tasks have completed, but $.when() doesn't appear to know how to handle an array of Deferred objects. "All done!" is happening first because the array is not a Deferred object, so jQuery goes ahead and assumes it's just done.

I know one could pass the objects into the function like $.when(deferred1, deferred2, ..., deferredX) but it's unknown how many Deferred objects there will be at execution in the actual problem I'm trying to solve.

like image 641
adamjford Avatar asked Apr 11 '11 20:04

adamjford


2 Answers

To pass an array of values to any function that normally expects them to be separate parameters, use Function.prototype.apply, so in this case you need:

$.when.apply($, my_array).then( ___ ); 

See http://jsfiddle.net/YNGcm/21/

In ES6, you can use the ... spread operator instead:

$.when(...my_array).then( ___ ); 

In either case, since it's unlikely that you'll known in advance how many formal parameters the .then handler will require, that handler would need to process the arguments array in order to retrieve the result of each promise.

like image 111
Alnitak Avatar answered Sep 21 '22 19:09

Alnitak


The workarounds above (thanks!) don't properly address the problem of getting back the objects provided to the deferred's resolve() method because jQuery calls the done() and fail() callbacks with individual parameters, not an array. That means we have to use the arguments pseudo-array to get all the resolved/rejected objects returned by the array of deferreds, which is ugly:

$.when.apply($,deferreds).then(function() {      var objects = arguments; // The array of resolved objects as a pseudo-array      ... }; 

Since we passed in an array of deferreds, it would be nice to get back an array of results. It would also be nice to get back an actual array instead of a pseudo-array so we can use methods like Array.sort().

Here is a solution inspired by when.js's when.all() method that addresses these problems:

// Put somewhere in your scripting environment if (typeof jQuery.when.all === 'undefined') {     jQuery.when.all = function (deferreds) {         return $.Deferred(function (def) {             $.when.apply(jQuery, deferreds).then(             // the calling function will receive an array of length N, where N is the number of             // deferred objects passed to when.all that succeeded. each element in that array will             // itself be an array of 3 objects, corresponding to the arguments passed to jqXHR.done:             // ( data, textStatus, jqXHR )             function () {                 var arrayThis, arrayArguments;                  if (Array.isArray(this)) {                     arrayThis = this;                     arrayArguments = arguments;                 }                 else {                     arrayThis = [this];                     arrayArguments = [arguments];                 }                  def.resolveWith(arrayThis, [Array.prototype.slice.call(arrayArguments)]);             },             // the calling function will receive an array of length N, where N is the number of             // deferred objects passed to when.all that failed. each element in that array will             // itself be an array of 3 objects, corresponding to the arguments passed to jqXHR.fail:             // ( jqXHR, textStatus, errorThrown )             function () {                 var arrayThis, arrayArguments;                  if (Array.isArray(this)) {                     arrayThis = this;                     arrayArguments = arguments;                 }                 else {                     arrayThis = [this];                     arrayArguments = [arguments];                 }                  def.rejectWith(arrayThis, [Array.prototype.slice.call(arrayArguments)]);             });         });     } } 

Now you can simply pass in an array of deferreds/promises and get back an array of resolved/rejected objects in your callback, like so:

$.when.all(deferreds).then(function(objects) {     console.log("Resolved objects:", objects); }); 
like image 42
crispyduck Avatar answered Sep 19 '22 19:09

crispyduck