8

So in using jQuery deferreds and $.when to load many objects in parallel.

$.when(
  a.ajax(), b.ajax(), c.ajax()
).then(
  //do something when all are complete
  complete();
);

Now, b.ajax() will sometimes fail, but I dont actually care. I just want to wait until all the calls have completed before calling complete().

Unfortunately, as soon as b fails, the when() rejects, and never fires the then()callback. This is AFAIK expected behaviour for $.when(), however dosent suit me in this case.

I effectively want a way to say:

$.when(
  a.ajax(), b.ajax().fail(return success), c.ajax()
).then(...)

Or perhaps there is a different way to use when(), or a more suitable construct?

alain.janinm
  • 19,951
  • 10
  • 65
  • 112
dalyons
  • 1,304
  • 3
  • 15
  • 23
  • @Ates I think your deleted answer can be fixed merely by resolving that new deferred object before you return. – Alnitak Jun 20 '11 at 15:48
  • possible duplicate of [$.Deferred: How to detect when every promise has been executed](http://stackoverflow.com/q/19177087/1048572) – Bergi Jul 14 '15 at 11:19

4 Answers4

6

If you want to capture the failure of a promise and convert that to a success, you can use the failFilter of then to return a resolved promise, like so:

deferredCall.then(function(answer) { 
   // this is success. you might transform the answer here.
   return transformed;
}, function() {
   // this is a fail. you might resolve the fail with an empty object.
   return $.Deferred().resolve({}).promise();
});

Doing this will ensure that the chain can continue past the failure unbroken.

So, for your example, you might do this:

$.when([
   a.ajax(),
   b.ajax().then(function(answer) { 
       return answer; 
   }, function() {
       return $.Deferred().resolve({}).promise();
   }),
   c.ajax()
]).then(function(results) {
    // etc.
});

Example 2: In my applications, I sometimes use then to get relational data for a particular entity and allow for the possibility of a 404 to indicate that no such relationship exists:

getEntity(id).then(function(entity) {
    return getAssociation(id).then(function(association) {
        entity.association = association;
        return entity;
    }, function() {
        entity.association = null;
        return $.Deferred().resolve(entity).promise();
    });
}).done(function(entity) {
    // etc.
});

Note, older answers suggest using the pipe method. This method is deprecated as of jQuery 1.8.

Griffin
  • 1,586
  • 13
  • 24
  • Any ideas why this wouldn't work?I tried it and it still goes to failure case down the pipe – Andrey Jan 14 '16 at 13:06
  • It is important that your fail handler (the second function of "then") return a resolved promise. A failed promise or any value not a promise will allow the chain to continue through to another fail handler. – Griffin Jan 14 '16 at 18:29
3

Here is something better than hacking a failure into a success.

Little known fact, $.when() will execute the then() callback immediately if any one of the parameters fails. It's by design. To quote the documentation:

http://api.jquery.com/jQuery.when/

In the multiple-Deferreds case where one of the Deferreds is rejected, jQuery.when immediately fires the failCallbacks for its master Deferred. Note that some of the Deferreds may still be unresolved at that point. If you need to perform additional processing for this case, such as canceling any unfinished ajax requests, you can keep references to the underlying jqXHR objects in a closure and inspect/cancel them in the failCallback.

There's actually no built-in way of waiting until all of them are finished regardless of their success/failure status.

So, I built a $.whenAll() for you :) It always waits until all of them resolve, one way or the other:

http://jsfiddle.net/InfinitiesLoop/yQsYK/

InfinitiesLoop
  • 14,349
  • 3
  • 31
  • 34
1

You could build $.onFailSucceed fairly easily by wrapping the $.Deferred object:

$.onCompleteSucceed = function(oldDfd) {
    var newDfd = $.Deferred();

    oldDfd.always(newDfd.resolve);

    return newDfd.promise();
}

You could then wrap the appropriate calls in this method:

$.when(
  a.ajax(), $.onCompleteSucceed(b.ajax()), c.ajax()
).then(...)
lonesomeday
  • 233,373
  • 50
  • 316
  • 318
1

So i figured it out in the end, see my answer to someone else with the same issue:

how to fool jqXHR to succeed always

lonesomeday's answer was neat, but not quite what i was after.

Community
  • 1
  • 1
dalyons
  • 1,304
  • 3
  • 15
  • 23
  • As of jQuery 1.8, pipe is deprecated. See write-up in [jQuery Deferred then](https://api.jquery.com/deferred.then/). – Griffin May 13 '15 at 15:39