jQuery.when – La richiamata per quando TUTTI i differiti non sono più “irrisolti” (risolti o rifiutati)?

Quando più oggetti Deferred vengono passati a jQuery.when , il metodo restituisce Promessa da un nuovo object “principale” Differito che tiene traccia dello stato aggregato di tutti i Differiti che è stato passato.

Il metodo sarà entrambi

  1. risolve il suo comandante differito non appena TUTTI i differiti si risolvono, o
  2. respingere il suo comandante differito non appena UNO dei differiti viene respinto.

Se il master Deferred è risolto (cioè TUTTI i Deferred risolvono), vengono passati i valori risolti di tutti i Deferred che sono stati passati a jQuery.when. Ad esempio, quando i Deferred sono richieste jQuery.ajax (), gli argomenti saranno gli oggetti jqXHR per le richieste, nell’ordine in cui sono stati assegnati nell’elenco degli argomenti:

$.when( $.getJSON('foo'), $.getJSON('bar') ).done(function(foo, bar) { // foo & bar are jqXHR objects for the requests }); 

Nel caso di più rinvii in cui uno dei differiti viene rifiutato, jQuery.quando IMMEDIATAMENTE INCENDI i callback di errore per il suo master differito, anche se alcuni dei differiti potrebbero ancora non essere risolti in quel punto:

 $.when( $.getJSON('foo'), $.getJSON('bar') ).fail(function(req) { // req is the jqXHR object for one of the failed requests }); 

Devo triggersre una richiamata quando tutti i Deferred passano a jQuery.quando non sono più “irrisolti” (cioè, tutti sono “risolti” o “rifiutati”). Potrei inviare oggetti JSON con 200 codici OK (invece di inviare JSON con 404 codici di stato errore non trovato) e determinare successo / errore nel metodo done (), ma preferirei mantenere la mia API RESTful. Come posso realizzare questo?

Penso che il modo più semplice per farlo sia quello di mantenere un object Deferred secondario in giro per ogni richiesta AJAX e assicurarsi che quello sia sempre risolto:

 var d1 = $.Deferred(); var d2 = $.Deferred(); var j1 = $.getJSON(...).complete(d1.resolve); var j2 = $.getJSON(...).complete(d2.resolve); $.when(j1, j2).done( only fires if j1 AND j2 are resolved ); $.when(d1, d2).done(function() { // will fire when j1 AND j2 are both resolved OR rejected // check j1.isResolved() and j2.isResolved() to find which failed }); 

Questo sta facendo uso del metodo AJAX .complete() aggiuntivo che jQuery aggiunge alle sue promesse per i metodi AJAX, che è chiamato sia per le promesse risolte sia per quelle rifiutate.

NB: d1.resolve funziona come un callback a sé stante, non ha bisogno di essere racchiuso in un blocco function() { ... } .

@ Risposta di Alnitak è intelligente e mi ha aiutato a cancellare un trucco che avevo creato in cui stavo in qualche modo artificialmente risolvendo una promise – indipendentemente dal risultato sottostante – in modo che potessi usare “quando” per raggruppare più richieste e usare “fatto” procedere indipendentemente dal loro successo / fallimento.

Sto “rispondendo” alla risposta di Alnitak nella speranza di fornire un altro uso per il suo suggerimento che supporta un numero arbitrario di promesse sottostanti.

 var asyncFunc, entity, entities, $deferred, $deferreds; // ... foreach (entity in entities) { $deferred = $.Deferred(); $deferreds.push($deferred); asyncFunc(entity).done(...).fail(...).always($deferred.resolve); } // ... $.when.apply($, $deferreds).done(...) 

Questo è pseudo-JavaScript ma dovrebbe trasmettere l’approccio. Per alcuni set di quadro di dimensioni arbitrarie, creare un rinvio ($ differito) per ogni entity framework e inserirlo in un array ($ differiti), effettuare la chiamata asincrona, aggiungere fatto / fallire come desiderato ma includere sempre un ‘sempre’ che risolve questo $ differita dell’entity framework. NB il ‘sempre’ riceve la funzione di risoluzione differita non la sua invocazione.

Il ‘quando’ converte l’array $ deferreds nella lista degli argomenti per ‘when’ e, poiché questo set di differimenti è garantito per risolvere (grazie al sempre), è ora ansible definire un ‘done’ che sarà invocato una volta tutti le chiamate asincrone sono completate indipendentemente dal fatto che questi siano riusciti o meno.

Di recente ho creato un plugin che potrebbe essere d’aiuto. Lo chiamo $.whenAll .

Questa estensione tratta tutti i successi e i fallimenti come eventi di progresso. Dopo che tutte le promesse sono state completate, la promise globale viene risolta se non ci sono stati errori. Altrimenti la promise globale viene respinta.

$ .when All – https://gist.github.com/4341799 ( test )

Esempio di utilizzo:

 $.whenAll($.getJSON('foo'), $.getJSON('bar')) .then( doneCallback ,failcallback // progress callback // the only problem is $.ajax.done/fail states call their callbacks // with params in different locations (except for state) ,function(data, state, jqXhr) { if (state == 'success') { // do happy stuff } else { // error (fail) // `data` is actually the jqXhr object for failed requests // `jqXhr` is the text of the error "Not Found" in this example } } ) ; 

La mia implementazione:

Codice plugin:

 jQuery.whenAll = function (deferreds) { var lastResolved = 0; var wrappedDeferreds = []; for (var i = 0; i < deferreds.length; i++) { wrappedDeferreds.push(jQuery.Deferred()); deferreds[i].always(function() { wrappedDeferreds[lastResolved++].resolve(arguments); }); } return jQuery.when.apply(jQuery, wrappedDeferreds).promise(); }; 

Per usarlo:

 jQuery.whenAll([jQuery.get('/your-resource'), jQuery.get('/your-resource')]) .done( function(result1, result2) { console.log(result1[1]); console.log(result2[1]); }); 

Dai un'occhiata al violino: http://jsfiddle.net/LeoJH/VMQ3F/

Ecco un plugin jQuery che ho apportato modificando il codice core attuale per $.when() per utilizzare la semantica. Per mancanza di un nome migliore si chiama $.myWhen() :

 (function($) { $.myWhen = function( subordinate /* , ..., subordinateN */ ) { var i = 0, responseValues = Array.prototype.slice.call( arguments ), length = responseValues.length, // the count of uncompleted subordinates remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0, // the master Deferred. If responseValues consist of only a single Deferred, just use that. deferred = remaining === 1 ? subordinate : jQuery.Deferred(), // Update function for all resolve, reject and progress values updateFunc = function( i, contexts, values ) { return function( value ) { contexts[ i ] = this; values[ i ] = arguments.length > 1 ? Array.prototype.slice.call( arguments ) : value; if( values === progressValues ) { deferred.notifyWith( contexts, values ); } else if ( !( --remaining ) ) { deferred.resolveWith( contexts, values ); } }; }, progressValues, progressContexts, responseContexts; // add listeners to Deferred subordinates; treat others as resolved if ( length > 1 ) { progressValues = new Array( length ); progressContexts = new Array( length ); responseContexts = new Array( length ); for ( ; i < length; i++ ) { if ( responseValues[ i ] && jQuery.isFunction( responseValues[ i ].promise ) ) { responseValues[ i ].promise() .always( updateFunc( i, responseContexts, responseValues ) ) .progress( updateFunc( i, progressContexts, progressValues ) ); } else { --remaining; } } } // if we're not waiting on anything, resolve the master if ( !remaining ) { deferred.resolveWith( responseContexts, responseValues ); } return deferred.promise(); }; })(jQuery); 

Basta inserire questo codice subito dopo aver caricato jQuery e la funzione $.myWhen() sarà disponibile insieme a $.when() . Tutto il resto è esattamente uguale al 100% ad eccezione della semantica.

Un miglioramento della soluzione di Leo Hernandez per casi d’uso più generali che non comportano semplicemente il recupero di risorse da un server, che ad esempio può includere eventi triggersti ​​da interazioni dell’utente o chiamate UI jQuery asincrone (ad esempio slideUp () e slideDown ()). Vedi https://jsfiddle.net/1trucdn3/ per casi d’uso avanzati.

 $.whenAll = function (deferreds) { var lastResolved = 0; var wrappedDeferreds = []; for (var i = 0; i < deferreds.length; i++) { wrappedDeferreds.push($.Deferred()); if (deferreds[i] && deferreds[i].always) { deferreds[i].always(wrappedDeferreds[lastResolved++].resolve); } else { wrappedDeferreds[lastResolved++].resolve(deferreds[i]); } } return $.when.apply($, wrappedDeferreds).promise(); }; 

Il miglioramento ci consente di passare valori non differiti nell'argomento array. Questo è qualcosa che potresti fare con $ .when (). Inoltre, ho ripulito l'output che si ottiene nella funzione di callback per essere più in linea con il metodo originale $ .when (), nel caso in cui si desideri semplicemente recuperare il risultato indipendentemente dallo stato. La soluzione di Leo passerebbe l'intero object posticipato di conseguenza, quindi dovrai scavare per trovare le informazioni di cui hai bisogno.

 $.whenAll([1, $.Deferred().resolve("Good"), $.Deferred().reject("Bad")]) .done(function (result1, result2, result3) { // result1 -> 1 // result2 -> "Good" // result3 -> "Bad" }); 

Le risposte di @Alnitak e @DazWilkin sono fantastiche! Ma personalmente preferisco lo stile funzionale, quindi ecco una versione funzionale per un numero arbitrario di promesse:

 var entities; // ... var deferreds = entities.map(function() { var deferred = $.Deferred(); asyncFunc(this).done(...).fail(...).always(deferred.resolve); return deferred; } // ... $.when.apply($, deferreds).done(...) 

Rispetto alla risposta @DazWilkin, utilizzo la funzione map anziché foreach .

Ho trovato una soluzione in cui ho 2 richieste in un momento in cui sono in grado di accedere ai singoli successi anche quando una delle richieste fallisce:

  $.when ( $.getJSON(...).then(function (results) { console.log('SUCCESS REQUEST 1 BY ITSELF', results); }), $.getJSON(...).then(function (results) { console.log('SUCCESS REQUEST 2 BY ITSELF', results); }) ).then ( function (results1, results2) { console.log('BOTH REQUESTS SUCCESSFUL...'); console.log('results1', results1); console.log('results2', results2); }, function (error1, error2) { console.log('AT LEAST 1 REQUEST FAILED...'); console.log('error1', error1); console.log('error2', error2); } );