Wie arbeiten Sie mit einem Array von jQuery Deferreds?


132

Ich habe eine Anwendung, bei der Daten in einer bestimmten Reihenfolge geladen werden müssen: die Stamm-URL, dann die Schemas und schließlich die Anwendung mit den Schemas und URLs für die verschiedenen Datenobjekte initialisieren. Während der Benutzer durch die Anwendung navigiert, werden Datenobjekte geladen, anhand des Schemas überprüft und angezeigt. Während der Benutzer die Daten CRUDs, bieten die Schemas eine First-Pass-Validierung.

Ich habe ein Problem mit der Initialisierung. Ich verwende einen Ajax-Aufruf, um das Stammobjekt $ .when () abzurufen und dann ein Array von Versprechungen zu erstellen, eines für jedes Schemaobjekt. Das funktioniert. Ich sehe den Abruf in der Konsole.

Ich sehe dann den Abruf für alle Schemas, so dass jeder Aufruf von $ .ajax () funktioniert. fetchschemas () gibt tatsächlich eine Reihe von Versprechungen zurück.

Diese letzte when () -Klausel wird jedoch niemals ausgelöst und das Wort "DONE" wird niemals auf der Konsole angezeigt. Der Quellcode für jquery-1.5 scheint zu implizieren, dass "null" als Objekt akzeptabel ist, das an $ .when.apply () übergeben werden soll, da wenn () ein internes Deferred () -Objekt erstellt, um die Liste zu verwalten, wenn kein Objekt vorhanden ist übergeben.

Dies funktionierte mit Futures.js. Wie sollte ein Array von jQuery Deferreds verwaltet werden, wenn dies nicht der Fall ist?

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

Ich habe fast ein identisches Problem, außer dass ich für jede Ajax-Abfrage in fetch_one eine "Erfolgs" -Methode auslösen muss, bevor "DONE" gedruckt wird. Wie würden Sie das machen? Ich habe versucht, .pipe nach "fetch_one" zu verwenden, aber das schien nicht zu funktionieren.
CambridgeMike

Antworten:


198

Du schaust nach

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

Dies wird auch funktionieren (für einen gewissen Wert der Arbeit wird gebrochener Ajax nicht behoben):

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

Sie möchten $stattdessen passieren, nulldamit sich thisinnen darauf $.whenbezieht jQuery. Es sollte für die Quelle keine Rolle spielen, aber es ist besser als vorbei zu gehen null.

Verspottet alle Ihre $ .ajax durch Ersetzen durch $.whenund das Beispiel funktioniert

Es ist also entweder ein Problem in Ihrer Ajax-Anfrage oder in dem Array, das Sie an fetch_schemas übergeben.


Danke dir. Wie unterscheidet sich diese Syntax von der von done (). Fail ()?
Elf Sternberg

2
@elf Sternberg, .then(a,b) === .done(a).fail(b)es ist eine faule Abkürzung. Sie können anrufen, .done(a).fail(b)wenn Sie möchten
Raynos

1
Oh, und die Verwendung von $ .when.apply ($, ...) und $ .when.apply (null, ...) scheint irrelevant zu sein. jQuery selbst hat keine versprechen () -Methode, daher wird es zugunsten eines intern generierten verzögerten Objekts ignoriert (jQuery 1.5, Zeile 943).
Elf Sternberg

1
@ElfSternberg ist es zwar irrelevant, aber für die Lesbarkeit muss ich keinen zweiten Blick darauf werfen $.when.apply($, .... Das nullbringt mich dazu zu warten "was, was?". Es ist eine Frage des Stils und der Codierungspraxis. Ich musste die Quelle lesen, um zu bestätigen, thisdass keine Nullreferenz in jQuery.when!
Raynos

7
Die Verwendung von null lässt mich denken, "ok, das ist eine Art Problemumgehung" (was es ist), wohingegen, wenn $ verwendet würde, meine Aufmerksamkeit darauf gelenkt würde, über wtf nachzudenken, für das $ war.
Danyal Aytekin

53

Die obige Problemumgehung (danke!) Behebt das Problem, dass die für die verzögerte resolve()Methode bereitgestellten Objekte wiederhergestellt werden, nicht richtig , da jQuery die done()und fail()-Rückrufe mit einzelnen Parametern aufruft , nicht mit einem Array. Das heißt, wir müssen das argumentsPseudo-Array verwenden, um alle aufgelösten / zurückgewiesenen Objekte zu erhalten, die vom Array der zurückgestellten zurückgesendet werden, was hässlich ist:

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

Da wir eine Reihe von Verzögerungen übergeben haben, wäre es schön, eine Reihe von Ergebnissen zurückzugewinnen. Es wäre auch schön, ein tatsächliches Array anstelle eines Pseudo-Arrays zurückzugewinnen, damit wir Methoden wie verwenden können Array.sort().

Hier ist eine Lösung, die von der Methode von when.js inspiriert ist when.all(), mit der diese Probleme behoben werden:

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

Jetzt können Sie einfach eine Reihe von zurückgestellten / versprochenen Versprechungen übergeben und eine Reihe von aufgelösten / abgelehnten Objekten in Ihrem Rückruf zurückerhalten, wie folgt:

$.when.all(promises).then(function(schemas) {
     console.log("DONE", this, schemas); // 'schemas' is now an array
}, function(e) {
     console.log("My ajax failed");
});

@crispyduck - wissen Sie, ob Sie 100% sicher sein können, dass die Reihenfolge der Array-Elemente in den "Schemas" var in then () immer in der Reihenfolge ist, in der der Ajax in der "Versprechen" -Vari im Wann aufruft ()?
Netpoetica

6
Dies sollte nur in jQuery integriert sein, aber - das jQuery-Team hat die Anforderung mehrmals abgelehnt. In der Zwischenzeit stellen die Leute hier immer wieder die Frage und öffnen ähnliche Tickets gegen jQuery. Am Ende haben wir überall eine Userland-Implementierung und / oder unangenehme Aufrufe, um apply()... loszulegen.
mindplay.dk

Danke für diese Lösung! Gibt es eine Möglichkeit, die erfolgreichen Elemente auch dann zu erhalten, wenn eines (oder mehrere) fehlgeschlagen sind?
Doktoreas

Nun, alles, was Sie hier getan haben, ist die versteckte argumentsManipulation in eine eigene Methode. Großartig für die Wiederverwendung, spricht aber nicht die "Hässlichkeit" an, mit der man umgehen muss arguments(man könnte leicht nur haben:var schemas=Array.prototype.slice.call(arguments);)
Cowbert

2
@crispyduck, sollte nicht deferred.fail(...)lesen deferred.reject(...)?
Bob S

19

Wenn Sie eine ES6-Version von Javascript verwenden Es gibt einen Spread-Operator (...), der ein Array von Objekten in durch Kommas getrennte Argumente konvertiert.

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

Weitere Informationen zum ES6-Spread-Operator https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator finden Sie hier


1
Jep. Obwohl diejenigen von uns, die Coffeescript oder einen seiner Nachkommen / Nachahmer verwenden, seit einiger Zeit Zugriff auf diesen Operator haben.
Elf Sternberg

0

verlängert sich mit diesem 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)
    }
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.