Comment travaillez-vous avec un tableau de jQuery Deferreds?

132

J'ai une application qui nécessite que les données soient chargées dans un certain ordre: l'URL racine, puis les schémas, puis enfin initialiser l'application avec les schémas et les URL des différents objets de données. Au fur et à mesure que l'utilisateur navigue dans l'application, les objets de données sont chargés, validés par rapport au schéma et affichés. Au fur et à mesure que l'utilisateur CRUDs les données, les schémas fournissent une validation de premier passage.

J'ai un problème avec l'initialisation. J'utilise un appel Ajax pour récupérer l'objet racine, $ .when (), puis je crée un tableau de promesses, une pour chaque objet de schéma. Ça marche. Je vois la récupération dans la console.

Je vois alors la récupération pour tous les schémas, donc chaque appel $ .ajax () fonctionne. fetchschemas () renvoie en effet un tableau de promesses.

Cependant, cette dernière clause when () ne se déclenche jamais et le mot "DONE" n'apparaît jamais sur la console. Le code source de jquery-1.5 semble impliquer que "null" est acceptable comme objet à passer à $ .when.apply (), comme when () construira un objet Deferred () interne pour gérer la liste si aucun objet n'est passé.

Cela a fonctionné en utilisant Futures.js. Comment gérer un tableau de jQuery Deferred, sinon comme ça?

    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);
        });
    });
Elfe Sternberg
la source
J'ai un problème presque identique, sauf que j'ai besoin de lancer une méthode "succès" pour chaque requête ajax dans fetch_one, avant que "DONE" ne soit imprimé. Comment feriez-vous cela? J'ai essayé d'utiliser .pipe après "fetch_one", mais cela ne semblait pas fonctionner.
CambridgeMike

Réponses:

198

Vous cherchez

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

Cela fonctionnera également (pour une certaine valeur de travail, cela ne corrigera pas un ajax cassé):

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

Vous voudrez passer $au lieu de nullfaire référence à l' thisintérieur . Cela ne devrait pas avoir d'importance pour la source mais c'est mieux que de passer$.whenjQuerynull .

Maquette de tous vos $ .ajax en les remplaçant par $.whenet l'exemple fonctionne

C'est donc soit un problème dans votre requête ajax, soit dans le tableau que vous passez à fetch_schemas.

Raynos
la source
Je vous remercie. En quoi cette syntaxe est-elle différente de done (). Fail ()?
Elf Sternberg le
2
@elf Sternberg, .then(a,b) === .done(a).fail(b)c'est une sténographie paresseuse. Vous pouvez appeler .done(a).fail(b)si vous voulez
Raynos
1
Oh, et l'utilisation de $ .when.apply ($, ...) et $ .when.apply (null, ...) ne semble pas pertinente. jQuery lui-même n'a pas de méthode promise (), il est donc ignoré au profit d'un objet Deferred généré en interne (jQuery 1.5, ligne 943).
Elf Sternberg le
1
@ElfSternberg n'est en effet pas pertinent, mais pour la lisibilité, je n'ai pas besoin de jeter un second coup d'œil $.when.apply($, .... Le nullme fait aller "attendre, quoi?". C'est une question de style et de pratique de codage. J'ai dû lire la source pour confirmer que thisje ne lancerais pas de référence nulle dans jQuery.when!
Raynos le
7
L'utilisation de null me fait penser `` ok, c'est une sorte de solution de contournement '' (ce qui est le cas), alors que si $ était utilisé, mon attention serait détournée vers la question de savoir à quoi sert le $.
Danyal Aytekin
53

La solution de contournement ci-dessus (merci!) Ne résout pas correctement le problème de la récupération des objets fournis à la resolve()méthode du différé car jQuery appelle les rappels done()et fail()avec des paramètres individuels, pas un tableau. Cela signifie que nous devons utiliser le argumentspseudo-tableau pour obtenir tous les objets résolus / rejetés retournés par le tableau de différés, ce qui est moche:

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

Puisque nous avons passé un tableau de différés, ce serait bien de récupérer un tableau de résultats. Ce serait également bien de récupérer un tableau réel au lieu d'un pseudo-tableau afin que nous puissions utiliser des méthodes comme Array.sort().

Voici une solution inspirée when.js l » when.all()méthode qui répond à ces problèmes:

// 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;
    }
}

Maintenant, vous pouvez simplement passer un tableau de différés / promesses et récupérer un tableau d'objets résolus / rejetés dans votre rappel, comme ceci:

$.when.all(promises).then(function(schemas) {
     console.log("DONE", this, schemas); // 'schemas' is now an array
}, function(e) {
     console.log("My ajax failed");
});
canard croustillant
la source
@crispyduck - savez-vous si vous pouvez être sûr à 100% que l'ordre des éléments du tableau dans la variable "schemas" dans then () sera toujours dans le même ordre que les appels ajax dans la variable "promises" du quand ()?
netpoetica
6
Cela devrait simplement être intégré à jQuery, mais - l'équipe jQuery a rejeté la demande plusieurs fois. Pendant ce temps, les gens continuent de poser la question ici et d'ouvrir des tickets similaires contre jQuery et nous nous retrouvons avec une implémentation userland partout et / ou des appels maladroits à apply()... allez comprendre.
mindplay.dk
Merci pour cette solution! Existe-t-il un moyen d'obtenir les éléments réussis également si l'un (ou plusieurs) échoue?
doktoreas
Eh bien, tout ce que vous avez fait ici est une argumentsmanipulation cachée dans sa propre méthode. Idéal pour la réutilisation, mais n'aborde pas la "laideur" d'avoir à faire face arguments(vous pourriez facilement avoir juste:var schemas=Array.prototype.slice.call(arguments);)
cowbert
2
@crispyduck, ne devrait pas deferred.fail(...)lire deferred.reject(...)?
Bob S
19

Si vous utilisez la version ES6 de javascript Il existe un opérateur de propagation (...) qui convertit un tableau d'objets en arguments séparés par des virgules.

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

En savoir plus sur l'opérateur de propagation ES6 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator trouver ici

pashaplus
la source
1
Ouaip. Bien que ceux d'entre nous qui utilisent Coffeescript ou l'un de ses descendants / imitateurs ont accès à cet opérateur depuis un certain temps maintenant.
Elf Sternberg
0

s'étend quand avec ce code:

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)
    }
}
APPELEZ-MOI TZ
la source