Jquery.When - Callback for When All Deferreds Are No Longer 'Unresolved' (Either Resolved or Rejected)

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

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

jQuery - .always() callback firing too soon

You need to prevent error(s) sending the promise returned by $.when.apply($, requests) down the error path.

This can be achieved by :

  • chaining .then() to your $.ajax() calls, rather than specifying "success" and "error" handlers as $.ajax() options.
  • handling errors by converting to success (as this is jQuery, you have to return a resolved promise from the error handler).

This approach also allows you to control the data that's eventually delivered to APP.didFinishGeocoding()

With a few assumptions, the general shape of your code should be as follows :

function foo () {//assume there's an outer function wrapper 
var errorMarker = '**error**';

var requests = addresses.map(function (address, i) {
return $.ajax({
dataType: 'json',
url: 'http://api.com/addresses/' + address
}).then(function (data, textStatus, jqXhr) { //success handler
return APP.didGetAddressJson(data, i, jqXhr); //whatever APP.didGetAddressJson() returns will appear as a result at the next stage.
}, function (jqXhr, textStatus, errorThrown) { // error handler
APP.didFailToGetAddressJson(errorThrown, i);
return $.when(errorMarker);//errorMarker will appear as a result at the next stage - but can be filtered out.
});
// make some more requests (handled by other success functions)
});

return $.when.apply($, requests).then(function() {
//first, convert arguments to an array and filter out the errors
var results = Array.prototype.slice.call(arguments).filter(function(r) {
return r !== errorMarker;
});

//then call APP.didFinishGeocoding() with the filtered results as individual arguments.
return APP.didFinishGeocoding.apply(APP, results);

//alternatively, call APP.didFinishGeocoding() with the filtered results as an array.
//return APP.didFinishGeocoding(results);
});
}

Tweak as necessary.

jQuery.when() not working as expected

you really dont need to use any deferred objects to track ajax calls, instead you can just use the promise object returned from $.ajax within $.when().

JQUERY CODE:

var getData = function(url, successFunction) {
return $.ajax({
url: url,
method: 'get',
dataType: 'json'
}).then(function(p) {
successFunction(p);
},function(p){
//process error using error callback,
//just like success callbacks
});
};

I order to process individual ajax calls you can use .then() instead of .done() & .fail(), because these will not return any promise object unlike .then() to track the same in .when().

Rest of your code will work as it is.

What jQuery forum says:

As of jQuery 1.8, the deferred.then() method returns a new promise that can filter the status and values of a deferred through a function, replacing the now-deprecated deferred.pipe() method. The doneFilter and failFilter functions filter the original deferred's resolved / rejected status and values. The progressFilter function filters any calls to the original deferred's notify or notifyWith methods. These filter functions can return a new value to be passed along to the promise's .done() or .fail() callbacks, or they can return another observable object (Deferred, Promise, etc) which will pass its resolved / rejected status and values to the promise's callbacks.

refence links :

  • http://api.jquery.com/jQuery.ajax/#jQuery-ajax-settings
  • http://api.jquery.com/deferred.then/

$.Deferred: How to detect when every promise has been executed

More sophisticated promise libraries have an allSettled() function like Q or Promise.settle like Bluebird.

In jQuery, you could implement such a function yourself as well and extend the $ namespace with it, but that will only be necessary if you need it often and performance-optimized.

A simpler solution would be to create a new promise for each of the ones you are waiting for, and fulfilling them even when the underlying one is rejected. Then you can use $.when() on them without problems. In short:

// using Underscore's .invoke() method:
$.when.apply(null, _.invoke(promises, "then", null, $.when)).done(…)

More stable:

$.when.apply($, $.map(promises, function(p) {
return p.then(null, function() {
return $.Deferred().resolveWith(this, arguments);
});
})).then(…);

You might change the then callbacks a bit to distinguish between fulfilled and rejected results in the final done.

Can I manually resolve a deferred object if a callback might need to reject it?

It seems like you goal is really just to separate out the functionality into two steps: validate the result of the ajax request, then handle the result. To do that, have an outer deferred that then gets replaced with an inner deferred that resolves or rejects based on the parameters returned by the ajax request.

var deferred = $.Deferred();

deferred.then(function(data){
// new deferred that we will be returning
var deferred = $.Deferred();
if (data.success == true) {
deferred.resolveWith(data.result);
}
else {
deferred.rejectWith(data.error);
}
// any following chained methods will affect this returned deferred object
return deferred;
}).then(
// done callback
function(result){
console.log("Resolved!");
console.dir(result);
},
// fail callback
function(error){
console.log("Rejected!");
console.dir(error)
}
);

var json = $.getJSON("/foo");
json.done(deferred.resolveWith);

Why master deferred in not being rejected early in $.when as soon as one of the Deferreds is rejected?

The thing is that evaluation is always synchronous. You cannot force anything in between. So in order to do that you have to force your functions into being asynchronous. You can achieve this by using setTimeout(..., 0);:

$.when.apply($, [
$.Deferred(function (deferred) {
setTimeout(function() {
console.log('first deferred');
deferred.resolve();
}, 0);
}),
$.Deferred(function (deferred) {
setTimeout(function() {
console.log('second deferred');
deferred.resolve();
}, 0);
}),
...
]);

Don't ever use $.when with synchronous code. That's simply pointless.

Note however that the order of setTimeout's is not defined. You may end up with strange results.



Related Topics



Leave a reply



Submit