tldr
tldr

Reputation: 12112

jquery '$.when': how to trigger 'done' callback even in case of error

Jquery's $.when function executes the 'done' callback when all promises are resolved, but not if any of them result in errors. How do I execute the 'done' callback once all the promises have had a result, either success or error?

Upvotes: 1

Views: 617

Answers (1)

Benjamin Gruenbaum
Benjamin Gruenbaum

Reputation: 276306

Jquery's $.when function executes the 'done' callback when all promises are resolved, but not if any of them result in errors.

Allow me to rephrase that in more accurate terminology.

jQuery's $.when takes a list of promises1 and returns a new promise. That new promise fulfills when all of those promises fulfill or rejects when any of those promises reject.

The function you're looking fall is called "Settle" and is unavailable in jQuery's promise implementation - luckily we can easily add it.

The strategy is:

  • Create a new promise explicitly.
  • Keep a counter started at n - the number of arguments.
  • For each argument:
    • If it's not a promise assign it immediately and decrement the counter.
    • If it's a promise wait for it to resolve and then decrement the counter.
  • When the counter reaches 0, resolve the promise.

$.settle = function(promises){
    var count = arguments.length; // how many we'll have to wait for
    var d = $.Deferred(); // the new deferred - representing the return value

    var results = []; // what we'll end up resolving with

    $.map(arguments, function(arg, i){
        if(typeof arg.then !== "function"){ // it's not a promise!
            results[i] = {status:"fulfilled", value: arg}; 
            count--; // and we don't have to wait for it
        } else {
            arg.then(function(value){
                results[i] = {status:"fulfilled", value: value };
                count--;
                if(count === 0) d.resolve.apply(d, results); // resolve promise
            }, function(e){
                results[i] = {status:"rejected", reason: e };
                count--;
                if(count === 0) d.resolve.apply(d, results); // resolve promise
            });
        }
    })
    return d.promise(); // return a promise over the deferred

}

The usage of the above should something like:

$.settle($.get("firstUrl"),$.get("secondUrl")).then(function(firstResult, secondResult){
     // access results here, values are in firstResult.value
     // and you can tell if it rejected or fulfilled in firstResult.status
);

I'm not a huge fan of the semantics here - but I've tried to keep them as close as possible to jQuery's semantics for $.when.

(1. or deferreds which are also promises in jQuery)

Upvotes: 1

Related Questions