javascript - then - jQuery.when-Devolución de llamada cuando TODOS los aplazados ya no están "sin resolver"(ya sea resuelto o rechazado)?
javascript when then (8)
Cuando se pasan múltiples objetos jQuery.when a jQuery.when , el método devuelve la promesa de un nuevo objeto diferido "maestro" que rastrea el estado agregado de todos los aplazados que se ha pasado.
El método será cualquiera
- resolver su maestro Diferido tan pronto como TODOS los Deferidos resuelvan, o
- rechazar su maestro Diferido tan pronto como se rechace UNO de los Deferidos.
Si se resuelve el maestro Diferido (es decir, TODOS los Deferidos se resuelven), se pasan los valores resueltos de todos los aplazados que se pasaron a jQuery.when. Por ejemplo, cuando las solicitudes Deferreds son jQuery.ajax (), los argumentos serán los objetos jqXHR para las solicitudes, en el orden en que se dieron en la lista de argumentos:
$.when( $.getJSON(''foo''), $.getJSON(''bar'') ).done(function(foo, bar) {
// foo & bar are jqXHR objects for the requests
});
En el caso de Deferreds múltiple donde se rechaza uno de los Deferreds, jQuery.when INMEDIATAMENTE ENFIENDE las devoluciones fallidas para su maestro aplazado, incluso si algunos de los aplazados aún no se han resuelto en ese punto:
$.when( $.getJSON(''foo''), $.getJSON(''bar'') ).fail(function(req) {
// req is the jqXHR object for one of the failed requests
});
Necesito disparar una devolución de llamada cuando todos los aplazados pasaron a jQuery.when ya no están "sin resolver" (es decir, todos están "resueltos" o "rechazados"). Podría enviar objetos JSON con 200 códigos OK (en lugar de enviar códigos de estado de error JSON con 404 no encontrados) y determinar el éxito / error en el método done (), pero preferiría mantener mi API RESTful. ¿Cómo puedo lograr esto?
¡Las respuestas de @Alnitak y @DazWilkin son geniales! Pero personalmente prefiero el estilo funcional así que aquí hay una versión funcional para un número arbitrario de promesas:
var entities;
// ...
var deferreds = entities.map(function() {
var deferred = $.Deferred();
asyncFunc(this).done(...).fail(...).always(deferred.resolve);
return deferred;
}
// ...
$.when.apply($, deferreds).done(...)
En comparación con la respuesta de @DazWilkin, utilizo la función de map
lugar de foreach
.
Aquí hay un plugin de jQuery que he creado modificando el código del núcleo real para $.when()
para usar su semántica. A falta de un mejor nombre se llama $.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);
Simplemente coloque este código justo después de haber cargado jQuery y la función $.myWhen()
estará disponible junto con $.when()
. Todo lo demás es 100% exactamente igual excepto por la semántica.
Creo que la manera más fácil de hacerlo es mantener un objeto Deferred
secundario para cada solicitud AJAX y asegurarse de que siempre se resuelva:
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
});
Esto está haciendo uso del método adicional AJAX .complete()
que jQuery agrega a sus promesas para los métodos AJAX, que se llama para promesas resueltas y rechazadas.
NB: d1.resolve
funciona como una devolución de llamada por derecho propio, no necesita estar envuelto en un bloque function() { ... }
.
Encontré una solución donde tengo 2 solicitudes en un cuándo y puedo acceder a éxitos individuales incluso cuando una de las solicitudes falla:
$.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);
}
);
La respuesta de @Alnitak es inteligente y me ayudó a borrar un truco que había creado en el que de alguna manera resolvía artificialmente una promesa, independientemente del resultado subyacente, para poder usar ''cuándo'' para agrupar varias solicitudes y usar ''hecho''. proceder independientemente de su éxito / fracaso.
Estoy "respondiendo" la respuesta de Alnitak con la esperanza de darle otro uso a su sugerencia que apoya una cantidad arbitraria de promesas subyacentes.
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(...)
Esto es pseudo-JavaScript, pero debe transmitir el enfoque. Para un conjunto de entidades de tamaño arbitrario, cree un diferido ($ diferido) para cada entidad y empújelo en un array ($ diferidos), realice la llamada asincrónica, agregue hecho / falle según lo deseado, pero siempre incluya un ''siempre'' que resuelva este $ diferido de la entidad. Nota: el ''siempre'' recibe la función de resolución diferida, no su invocación.
El ''cuándo'' convierte la matriz $ diferidos en la lista de argumentos para ''cuándo'' y, dado que se garantiza que se resolverá este conjunto de aplazamientos (gracias a siempre), ahora es posible definir un ''hecho'' que se invocará una vez que todos las llamadas asincrónicas se completan independientemente de si tienen éxito o no.
Mi implementación:
Código de complemento:
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();
};
Para usarlo:
jQuery.whenAll([jQuery.get(''/your-resource''), jQuery.get(''/your-resource'')])
.done(
function(result1, result2) {
console.log(result1[1]);
console.log(result2[1]);
});
Mira el violín: http://jsfiddle.net/LeoJH/VMQ3F/
Recientemente he creado un complemento que puede ayudar. Lo llamo $.whenAll
.
Esta extensión trata todos los éxitos y fracasos como eventos de progreso. Después de que todas las promesas se hayan completado, la promesa global se resuelve si no hubo errores. De lo contrario, la promesa global es rechazada.
$ .whenAll - https://gist.github.com/4341799 ( tests )
Uso de muestra:
$.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
}
}
)
;
Una mejora en la solución de Leo Hernández para casos de uso más generales que no implican simplemente obtener recursos de un servidor, que por ejemplo puede incluir eventos desencadenados por interacciones del usuario, o llamadas asincrónicas de jQuery UI (por ejemplo, slideUp () y slideDown ()). Ver https://jsfiddle.net/1trucdn3/ para el caso de uso mejorado.
$.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();
};
La mejora nos permite pasar valores no diferidos al argumento de matriz. Esto era algo que podrías hacer con $ .when (). Además, limpie la salida que obtienes en la función de devolución de llamada para estar más en línea con el funcionamiento del método $ .when () original, en caso de que solo quieras recuperar el resultado independientemente del estado. La solución de Leo pasaría todo el objeto diferido como resultado, en la que luego deberá investigar para encontrar la información que necesita.
$.whenAll([1, $.Deferred().resolve("Good"), $.Deferred().reject("Bad")])
.done(function (result1, result2, result3) {
// result1 -> 1
// result2 -> "Good"
// result3 -> "Bad"
});