0

I've trouble synchronizing finished Deferreds with $.when(). I want to be notified when all deferred are finished wether resolved or failed.

My problem is when().always() that fires at first fail and doesn't wait other deferred to complete. Not sure if it's a bug or not.

I've made an example and here's a JsFiddle: http://jsfiddle.net/m3REv/

the js code from it:

/* our multiple deferred we'd like to sync. */
var def1 = $.Deferred();
var def2 = $.Deferred();
var def3 = $.Deferred();

def1.done( function() { logger.log('1 done');} ).fail( function() {logger.log('1 fail');} );
def2.done( function() { logger.log('2 done');} ).fail( function() {logger.log('2 fail');} );
def3.done( function() { logger.log('3 done');} ).fail( function() {logger.log('3 fail');} );

$.when( def1, def2, def3 ).then( function() {
    logger.log('w then');
} ).done( function() {
    logger.log('w done');
} ).fail( function() {
    logger.log('w fail');
} ).always( function() {
    logger.log('w always');
});


def1.reject();
def2.resolve();
def3.resolve();

and the output is:

1 fail
w fail
w always
2 done
3 done
BiAiB
  • 12,932
  • 10
  • 43
  • 63
  • 1
    It's not a bug. [The docs](http://api.jquery.com/jQuery.when/) state that `when()` fails its promise when the first observed promise fails. Failing the promise returned by `when()` _is_ resolving it, so its `always()` fires. Not sure how to work around that though, you might need to implement your own `when`-like construct. – lanzz Sep 12 '12 at 16:49
  • well, it's a lack then. I finally found a ticket related: http://bugs.jquery.com/ticket/9386 but they won't implement something until someone come up with a plugin first. _sigh_ – BiAiB Sep 13 '12 at 08:20

2 Answers2

1

workaround:

var defCount = 3, state = 0;
var overallAlways = function () {
    if (++state < defCount) return;
    logger.log('correct always');
};

def1.done( function() { logger.log('1 done');} )
    .fail( function() {logger.log('1 fail');} )
    .always(overallAlways);
def2.done( function() { logger.log('2 done');} )
    .fail( function() {logger.log('2 fail');} )
    .always(overallAlways);
def3.done( function() { logger.log('3 done');} )
    .fail( function() {logger.log('3 fail');} )
    .always(overallAlways);

Source

Or somethink like

function overallAlways(defObjects, callback) {
    var defCount = defObjects.length, state = 0;
    var alwaysCallback = function () {
        if (++state < defCount) return;
        callback.call(this);
    }
    $.each(defObjects, function (i, def) {
        def.always(alwaysCallback);
    });
}

var defs = [def1, def2, def3];
overallAlways(defs, function(){
    logger.log("overall always");
});

Source

Taras Taday
  • 131
  • 5
  • well, that's a working workaround, but I wish I had'nt have to bind always callback on each deferred. – BiAiB Sep 13 '12 at 07:45
  • You don't have to, you can call the `overallAlways` in both `done()` and `fail()` handlers instead, but it violates the DRY principle. – lanzz Sep 13 '12 at 08:27
  • thanks you for your help. I've rewritten the $.when anyway (see my own answer). Now I can sleep peacefully. – BiAiB Sep 13 '12 at 08:54
0

Finally I wrote a plugin which is a slightly modified version of jQuery.when(). I haven't tested it exhaustively, but it works for the fiddle and my personnal use for now.

here's the fiddle with the plugin plus the example:

http://jsfiddle.net/LTsLJ/

BiAiB
  • 12,932
  • 10
  • 43
  • 63