$ .Deferred: come rilevare quando ogni promise è stata eseguita

Ho un numero di compiti asincroni che devono essere completati, quindi sto usando le promesse.

Devo rilevare quando ciascuna delle promesse è stata eseguita (sia risolta che respinta). Non devo continuare l’esecuzione fino a quel momento.

Stavo usando qualcosa del genere:

$.when(promise1, promise2, ...).always(); 

Ma questo codice è sbagliato, perché il metodo when ha una valutazione lenta e ritorna non appena una delle promesse fallisce. Quindi la richiamata always viene eseguita non appena una delle promesse fallisce.

Stavo pensando di programmare una soluzione alternativa, ma questo caso d’uso è così comune che forse qualcuno lo ha già fatto, o forse c’è anche un modo per farlo usando solo jQuery (se no, sarebbe bello aggiungere un Promise.whenNonLazy o una Promise.when(promise1, promise2, ..., false) in futuro.

È ansible?

Le librerie di promesse più sofisticate hanno una funzione allSettled() come Q o Promise.settle come Bluebird .

In jQuery, potresti implementare anche tu una funzione di questo tipo ed estendere il $ namespace con esso, ma ciò sarà necessario solo se ne hai bisogno spesso e ottimizzato per le prestazioni.

Una soluzione più semplice sarebbe quella di creare una nuova promise per ciascuno di quelli che stai aspettando e soddisfarli anche quando il sottostante viene rifiutato. Quindi puoi usare $.when() su di loro senza problemi. In breve:

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

Più stabile:

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

È ansible modificare leggermente i callback per distinguere tra risultati soddisfatti e respinti nel finale.

Fucina,

Per prima cosa supponiamo che le tue promesse siano in un array.

 var promises = [....]; 

Ciò che sembra che sia, .when() applica ad alcune trasformazioni di queste promesse, tale che qualsiasi promise respinta viene convertita in soluzione, pur essendo trasparente alle promesse che sono già state risolte.

L’operazione richiesta può essere scritta in modo molto succinto come segue:

 $.when.apply(null, $.map(promises, resolvize)).done(...); //or, if further filtering by .then() is required ... $.when.apply(null, $.map(promises, resolvize)).then(...); 

dove resolvize è il meccanismo di trasformazione.

Quindi cosa dovrebbe resolvize() , assomiglia? .then() le caratteristiche di .then() per rendere la distinzione tra una promise risolta e una respinta e rispondere di conseguenza.

 function resolvize(promise) { //Note: null allows a resolved promise to pass straight through unmolested; return promise.then(null, function() { return $.Deferred().resolve.apply(null, arguments).promise(); }); } 

non testato

Con resolvize in qualche ambito esterno, può essere reso disponibile per essere usato in $.when.apply($.map(promises, resolvize)) ovunque sia necessario. Questo è molto probabilmente adeguato, senza andare al punto di estendere jQuery con un nuovo metodo.

Indipendentemente da come viene realizzata la trasformazione, si finisce con un potenziale problema; ovvero conoscere per ogni argomento del callback .done() , se la sua promise corrispondente è stata originariamente risolta o rifiutata. Questo è il prezzo che si paga per convertire il rifiuto in risoluzione. Tuttavia, potresti essere in grado di rilevare lo stato originale dai parametri con i quali le promesse originali sono state risolte / rifiutate.

Questa è una proprietà interessante di always – non mi aspettavo questo comportamento.

Suppongo che potresti usare un master, di livello superiore, differito per monitorare gli stati dei differiti principali, che viene risolto solo dopo che i differenziali principali sono stati tutti risolti o respinti. Qualcosa di simile a:

 //set up master deferred, to observe the states of the sub-deferreds var master_dfd = new $.Deferred; master_dfd.done(function() { alert('done'); }); //set up sub-deferreds var dfds = [new $.Deferred, new $.Deferred, new $.Deferred]; var cb = function() { if (dfds.filter(function(dfd) { return /resolved|rejected/.test(dfd.state()); }).length == dfds.length) master_dfd.resolve(); }; dfds.forEach(function(dfd) { dfd.always(cb); }); //resolve or reject sub-deferreds. Master deferred resolves only once //all are resolved or rejected dfds[0].resolve(); dfds[1].reject(); dfds[2].resolve(); 

Fiddle: http://jsfiddle.net/Wtxfy/3/