¿Cómo trabaja con una serie de jQuery Deferreds?

132

Tengo una aplicación que requiere que los datos se carguen en un cierto orden: la URL raíz, luego los esquemas, luego finalmente inicialice la aplicación con los esquemas y las URL para los diversos objetos de datos. A medida que el usuario navega por la aplicación, los objetos de datos se cargan, validan contra el esquema y se muestran. A medida que el usuario CRUD los datos, los esquemas proporcionan validación de primer paso.

Tengo un problema con la inicialización. Utilizo una llamada Ajax para recuperar el objeto raíz, $ .when (), y luego creo una serie de promesas, una para cada objeto de esquema. Eso funciona. Veo la búsqueda en la consola.

Luego veo la búsqueda de todos los esquemas, por lo que cada llamada $ .ajax () funciona. fetchschemas () de hecho devuelve una serie de promesas.

Sin embargo, esa última cláusula when () nunca se activa y la palabra "DONE" nunca aparece en la consola. El código fuente de jquery-1.5 parece implicar que "nulo" es aceptable como un objeto para pasar a $ .when.apply (), como cuando () construirá un objeto interno diferido () para administrar la lista si no hay ningún objeto aprobada en.

Esto funcionó usando Futures.js. ¿Cómo se debe gestionar una matriz de jQuery Deferreds, si no es así?

    var fetch_schemas, fetch_root;

    fetch_schemas = function(schema_urls) {
        var fetch_one = function(url) {
            return $.ajax({
                url: url,
                data: {},
                contentType: "application/json; charset=utf-8",
                dataType: "json"
            });
        };

        return $.map(schema_urls, fetch_one);
    };

    fetch_root = function() {
        return $.ajax({
            url: BASE_URL,
            data: {},
            contentType: "application/json; charset=utf-8",
            dataType: "json"
        });
    };

    $.when(fetch_root()).then(function(data) {
        var promises = fetch_schemas(data.schema_urls);
        $.when.apply(null, promises).then(function(schemas) {
            console.log("DONE", this, schemas);
        });
    });
Elf Sternberg
fuente
Tengo un problema casi idéntico, excepto que necesito activar un método de "éxito" para cada consulta ajax en fetch_one, antes de imprimir "HECHO". ¿Cómo harías para hacer esto? Intenté usar .pipe después de "fetch_one", pero eso no pareció funcionar.
CambridgeMike

Respuestas:

198

Estas buscando

$.when.apply($, promises).then(function(schemas) {
     console.log("DONE", this, schemas);
}, function(e) {
     console.log("My ajax failed");
});

Esto también funcionará (por algún valor de trabajo, no reparará ajax roto):

$.when.apply($, promises).done(function() { ... }).fail(function() { ... });` 

Querrás pasar en $lugar de nullpara que thisdentro se $.whenrefiera jQuery. No debería importar a la fuente, pero es mejor que pasarnull .

Se burló de todos sus $ .ajax reemplazándolos con $.wheny la muestra funciona

Por lo tanto, es un problema en su solicitud ajax o la matriz que pasa a fetch_schemas.

Raynos
fuente
Gracias. ¿Cómo es esta sintaxis diferente de done (). Fail ()?
Elf Sternberg
2
@elf Sternberg, .then(a,b) === .done(a).fail(b)es una taquigrafía perezosa. Puedes llamar .done(a).fail(b)si quieres
Raynos
1
Ah, y el uso de $ .when.apply ($, ...) y $ .when.apply (null, ...) parece ser irrelevante. jQuery en sí no tiene un método de promesa (), por lo que se ignora a favor de un objeto diferido generado internamente (jQuery 1.5, línea 943).
Elf Sternberg
1
@ElfSternberg es realmente irrelevante, pero para facilitar la lectura no necesito echar un segundo vistazo $.when.apply($, .... El nullme hace ir "espera, ¿qué?". Es una cuestión de estilo y práctica de codificación. Tuve que leer la fuente para confirmar thisque no arrojaría una referencia nula dentro de jQuery.cuando!
Raynos
77
El uso de nulo me hace pensar 'ok, esta es una especie de solución' (lo cual es), mientras que si se usara $ mi atención se desviaría a pensar en lo que era para $.
Danyal Aytekin
53

La solución anterior (¡gracias!) No aborda adecuadamente el problema de recuperar los objetos proporcionados al resolve()método diferido porque jQuery llama a done()y fail()devoluciones de llamada con parámetros individuales, no una matriz. Eso significa que tenemos que usar la argumentspseudo-matriz para obtener todos los objetos resueltos / rechazados devueltos por la matriz de diferidos, lo cual es feo:

$.when.apply($, promises).then(function() {
     var schemas=arguments; // The array of resolved objects as a pseudo-array
     ...
};

Como aprobamos una serie de diferidos, sería bueno obtener una variedad de resultados. También sería bueno recuperar una matriz real en lugar de una pseudo-matriz para que podamos usar métodos comoArray.sort() .

Aquí hay una solución inspirada en el método de when.jswhen.all() que aborda estos problemas:

// Put somewhere in your scripting environment
if (jQuery.when.all===undefined) {
    jQuery.when.all = function(deferreds) {
        var deferred = new jQuery.Deferred();
        $.when.apply(jQuery, deferreds).then(
            function() {
                deferred.resolve(Array.prototype.slice.call(arguments));
            },
            function() {
                deferred.fail(Array.prototype.slice.call(arguments));
            });

        return deferred;
    }
}

Ahora puede simplemente pasar una matriz de diferidos / promesas y recuperar una matriz de objetos resueltos / rechazados en su devolución de llamada, de esta manera:

$.when.all(promises).then(function(schemas) {
     console.log("DONE", this, schemas); // 'schemas' is now an array
}, function(e) {
     console.log("My ajax failed");
});
Pato crujiente
fuente
@crispyduck: ¿sabe si puede estar 100% seguro de que el orden de los elementos de la matriz en los "esquemas" var in then () siempre estará en el mismo orden que las llamadas ajax en las var "promesas" en el ()?
netpoetica
66
Esto debería estar integrado en jQuery, pero el equipo de jQuery ha rechazado la solicitud varias veces. Mientras tanto, la gente sigue haciendo la pregunta aquí y abriendo tickets similares contra jQuery y terminamos con una implementación de tierra de usuario en todas partes y / o llamadas incómodas para apply()... imagínense.
mindplay.dk
¡Gracias por esta solución! ¿Hay alguna manera de obtener los elementos exitosos también si uno (o más) falla?
doktoreas
bueno, todo lo que has hecho aquí es la argumentsmanipulación oculta en su propio método. Genial para la reutilización, pero no aborda la "fealdad" de tener que lidiar arguments(fácilmente podría haber simplemente:var schemas=Array.prototype.slice.call(arguments);)
cowbert
2
@crispyduck, no debería deferred.fail(...)leer deferred.reject(...)?
Bob S
19

Si está utilizando la versión ES6 de javascript Hay un operador de propagación (...) que convierte la matriz de objetos en argumentos separados por comas.

$.when(...promises).then(function() {
 var schemas=arguments; 
};

Más información sobre el operador de propagación ES6 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator encontrar aquí

pashaplus
fuente
1
Sip. Aunque aquellos de nosotros que usamos Coffeescript o uno de sus descendientes / imitadores hemos tenido acceso a ese operador por un tiempo.
Elf Sternberg
0

se extiende cuando con este código:

var rawWhen = $.when
$.when = function(promise) {
    if ($.isArray(promise)) {
        var dfd = new jQuery.Deferred()
        rawWhen.apply($, promise).done(function() {
            dfd.resolve(Array.prototype.slice.call(arguments))
        }).fail(function() {
            dfd.reject(Array.prototype.slice.call(arguments))
        })
        return dfd.promise()
    } else {
        return rawWhen.apply($, arguments)
    }
}
LLAME ME TZ
fuente