1

Check the following example

$.when(a1, a2, a3, a4, .....)
       .done(function (){alert('done')})
       .fail(function (){alert('fail')});

If all a1-ax were successful, I can retrieve and handle all the data within the "done" function for all "promises"

If a1 and a2 failed, while a3 and a4 were successful and returned some data. "fail" is called and doesn't say much about what actually failed, neither does it pass successful values (doesn't pass any arguments)

Question Is there a way to retrieve the values (data) of successful calls (a3 and a4) through the "Deferred" (even if it fails), or should I assign individual callbacks to each promise for that purpose ?

man
  • 197
  • 9
  • You don't need "individual" callbacks, you can just extract the callbacks to named functions and assign each promise to those two callbacks. – Ingo Bürk Apr 27 '14 at 09:55
  • When the fail callback is called, it should say *why* and *what* failed - even though it doesn't say *which* of the `aN` promises failed. – Bergi Apr 27 '14 at 12:33
  • possible duplicate of [$.Deferred: How to detect when every promise has been executed](http://stackoverflow.com/questions/19177087/deferred-how-to-detect-when-every-promise-has-been-executed) – Bergi Apr 27 '14 at 12:33

1 Answers1

1

$.when() is designed to immediately execute .fail() if any of the promises passed to it is rejected. Thus, if a1 or a2 is rejected, it will call .fail() and probably not yet have the data for a3 and a4. That's just how it is designed which does limit its usefulness in some cases. If you want to know each individual outcome regardless of whether one of the other promises fails and you're using $.when(), then as you surmised, you will need to attached your own .done() and .fail() handlers to each individual promise.

In a coincidence for you, I have implemented (for my own use) a MultiDeferred object that allows you to specify whether you want it to fail immediately if any promise fails (as $.when() does) or wait until all promises have reached their final outcome before calling the master .fail() or .done(). This might be useful for you.

This is a summary of the difference in features vs. $.when():

  1. You can configure it to not call it's own .fail() until all promises have finished with the .setFailImmediate(false) method.
  2. It has a .add() method so you can add more promises to it (before it is resolved). Since these promises are often collected in a loop, this makes it easier to just .add() one rather than collect them all in an array that you can then pass to $.when().
  3. .progress() works differently. Rather than echo the .progress() of each individual promise, this does a .notify() which triggers a .progress() when each individual promise is either rejected or resolved so you can track each individual completion. If you want the actual progress of each individual promise, you can always just attach to them directly.
  4. You can query the deferred at any time as to whether any promises have failed yet with .getFailYet().
  5. You can query how many promises have yet to completed with .getRemaining().

Note the constructor $.MultiDeferred() returns an actual Deferred object (with a few extra methods added) and you can also call .promise() on it to get just the promise object (that will also have extra methods on it).

The MultiDeferred object can be used like this:

var d = $.MultiDeferred(a1, a2, a3, a4);
// configure it to not fail immediately when any single promise fails
d.setFailImmediate(false);
// you can optionally add more promises here
// often in a loop
d.add(a5, a6);
d.done(function() {
    // called when all promises have been successfully resolved
}).fail(function() {
    // even if one promise failed, all the data from all the promises
    // is passed here as this can be configured to wait for all
    // promises to finish before calling .fail()
});

Working demo: http://jsfiddle.net/jfriend00/3JN52/

The code for the MultiDeferred object is here:

jQuery.MultiDeferred = function(/* zero or more promises */) {

    // make the Deferred
    var self = jQuery.Deferred();
    // get the promise so new methods can be added to it too
    var promise = self.promise();

    var remainingToFinish = 0;
    // two parallel arrays that contain the promises that are added
    // and the arguments passed to reject() or resolve()
    // so when we call the master reject() or resolve(), we can pass all those args on through
    var promises = [];
    var args = [];

    // keep track of what to do upon failure and whether we had any failures
    var anyFail = false;
    var failImmediate = false;

    function _add(p) {
        // save our index in a local variable so it's available in this closure later
        // so we know what the array index was of this particular promise when it fires
        var index = promises.length;

        // save this promise into our array structure
        promises.push(p);
        // push placeholder in the args array
        args.push([null]);

        // one more waiting to finish
        ++remainingToFinish;

        // see if all the promises are done
        function checkDone(fail) {
            return function() {
                anyFail |= fail;
                // make copy of arguments so we can save them
                args[index] = Array.prototype.slice.call(arguments, 0);
                --remainingToFinish;

                // send notification that one has finished
                self.notify.apply(self, args[index]);
                // if all promises are done, then resolve or reject
                if (self.state() === "pending" && (remainingToFinish === 0 || (fail && failImmediate))){
                    var method = anyFail ? "reject" : "resolve";
                    self[method].apply(self, args);
                }
            }
        }
        // add our own monitors so we can collect all the data
        // and keep track of whether any fail
        p.when(checkDone(false), checkDone(true));
    }

    self.add = function(/* one or more promises or arrays of promises */) {
        if (this.state() !== "pending") {
            throw "Can't add to a deferred that is already resolved or rejected";
        }
        var arg;
        for (var i = 0; i < arguments.length; i++) {
            // if the next arg is not an array, then put it in an array
            // so the rest of the code can just treat it like an array
            arg = arguments[i];
            if (!Array.isArray(arg)) {
                arg = [arg];
            }
            for (var j = 0; j < arg.length; j++) {
                _add(arg[j]);
            }
        }
        return this;
    }

    // set whether the deferred should reject immediately if any subordinate
    // promises fails or whether it should wait until all have completed
    // default is to wait until all have completed
    self.setFailImmediate = function(failQuick) {
        failImmediate = failQuick;
        return this;
    }

    // only query methods are on the promise object 
    // get count of remaining promises that haven't completed yet
    self.getRemaining = promise.getRemaining = function() {
        return remainingToFinish;
    }

    // get boolean on whether any promises have failed yet
    self.getFailYet = promise.getFailYet = function() {
        return anyFail;
    }

    // add any promises passed to the constructor
    self.add.apply(self, arguments);
    return self;    
};
jfriend00
  • 683,504
  • 96
  • 985
  • 979