Search code examples
jquerywaitjquery-deferred

Wait until everything executes even failures using $.when()


With jQuery.when(), if one request fails then they all fail.

$.when(deferred1, deferred2).then(function() {
    // Called only after both deferred are resolved
}, function() {
    // Called immediately after one of the deferreds is rejected
});

How can you wait for everything to execute even failures? I tried to use .always() instead of .then() but the callback is also being called right after the first failure.

One (ugly) solution would be to have flags to control when both operations are completed and use the .always() on each of them (without $.when()).

var deferred1Complete = false;
var deferred2Complete = false;

function callback() {
    if (deferred1Complete && deferred2Complete) {
        // Execute after both deferred are either resolved or rejected
    }
}

deferred1.always(function() {
    deferred1Complete = true;
    callback();
});

deferred2.always(function() {
    deferred2Complete = true;
    callback();
});

Is there a better way to do this?


Solution

  • Jonatan,

    I like your solution and offer a few improvements to :

    • phrase as jQuery utility method, like $.when()
    • allow individual promises or an array of promises to be passed in
    • resolve immediately if no arguments or an empty array is passed in
    • be tolerant of non-promises/non-deferreds
    • allow progress of the chain to be reported as each promise is resolved or rejected.

    Here's the code :

    (function($) {
        $.whenAlways = function(chain) {
            if(!chain || !$.isArray(chain)) chain = $.extend([], arguments);
            var n = chain.length;
            return $.Deferred(function(deferred) {
                $.each(chain, function(i, promise) {
                    if(!promise.always) n--;
                    else {
                        promise.always(function() {
                            deferred.notify(--n);
                            if(n == 0) deferred.resolve();
                        });
                    }
                });
                if(n == 0) deferred.resolve();
            }).promise();
        }
    })(jQuery);
    

    This approach would allow any progressCallbacks (added to the retuned promise) to be called whenever a chain promise is reolved/rejected, regardless of reolution/rejection order. By passing the number of outstanding chain promises to the progressCallbacks, you could, for example, provide a countdown indication or respond to intermediate countdown milestones.

    Seems to work - DEMO