Winkel $ q, Wie mehrere Versprechen innerhalb und nach einer for-Schleife verkettet werden

75

Ich möchte eine for-Schleife haben, die bei jeder Iteration asynchrone Funktionen aufruft.

Nach der for-Schleife möchte ich einen weiteren Codeblock ausführen, aber nicht bevor alle vorherigen Aufrufe in der for-Schleife aufgelöst wurden.

Mein Problem im Moment ist, dass entweder der Codeblock nach der for-Schleife ausgeführt wird, bevor alle asynchronen Aufrufe beendet sind, oder dass er überhaupt nicht ausgeführt wird.

Der Codeteil mit der FOR-Schleife und dem Codeblock danach (den vollständigen Code finden Sie unter Geige ):

[..]
function outerFunction($q, $scope) {
    var defer = $q.defer();    
    readSome($q,$scope).then(function() {
        var promise = writeSome($q, $scope.testArray[0])
        for (var i=1; i < $scope.testArray.length; i++) {
             promise = promise.then(
                 angular.bind(null, writeSome, $q, $scope.testArray[i])
             );                                  
        } 
        // this must not be called before all calls in for-loop have finished
        promise = promise.then(function() {
            return writeSome($q, "finish").then(function() {
                console.log("resolve");
                // resolving here after everything has been done, yey!
                defer.resolve();
            });   
        });        
    });   

    return defer.promise;
}

Ich habe eine jsFiddle erstellt, die hier zu finden ist: http://jsfiddle.net/riemersebastian/B43u6/3/ .

Im Moment sieht es so aus, als ob die Ausführungsreihenfolge in Ordnung ist (siehe Konsolenausgabe).

Ich vermute, das liegt einfach daran, dass jeder Funktionsaufruf sofort zurückkehrt, ohne echte Arbeit zu leisten. Ich habe versucht, die defer.resolve mit setTimeout zu verzögern, bin jedoch fehlgeschlagen (dh der letzte Codeblock wurde nie ausgeführt). Sie können es im auskommentierten Block in der Geige sehen.

Wenn ich die realen Funktionen verwende, die in eine Datei schreiben und aus einer Datei lesen, wird der letzte Codeblock ausgeführt, bevor der letzte Schreibvorgang abgeschlossen ist, was ich nicht möchte.

Natürlich könnte der Fehler in einer dieser Lese- / Schreibfunktionen liegen, aber ich möchte überprüfen, ob mit dem hier veröffentlichten Code nichts falsch ist.

SebastianRiemer
quelle
(1) Informationen zu den Funktionen, die Sie innerhalb der Schleife aufrufen : Müssen sie nacheinander ausgeführt werden oder sind sie parallel, sodass der letzte Block nach Abschluss aller Funktionen noch ausgeführt werden muss? Und: (2) Was soll passieren, wenn einer von ihnen zu einem Fehler führt?
Nikos Paraskevopoulos
Wenn Sie eine Schreibfunktion verwenden, sind diese häufig auch asynchron, sodass es sehr wahrscheinlich ist, dass alles "wie beabsichtigt" funktioniert. Das heißt, Angular startet alle Schreibvorgänge (was einen Bruchteil der Zeit in Anspruch nimmt), aber die Schreibvorgänge selbst dauern lange. Was schreiben Sie und welche API verwenden Sie?
Hylianpuffball
@NikosParaskevopoulos (1) Es ist mir egal, ob sie parallel oder nacheinander ausgeführt werden, sie könnten parallel ausgeführt werden, da sie nicht voneinander abhängen. Derzeit gibt jede innere Funktion ein Versprechen zurück und wird am Ende der Operation aufgelöst, dh sie wird seriell ausgeführt. Sie haben es verstanden, die letzte Operation muss immer die letzte ausgeführte Operation sein, unabhängig davon, ob die vorherige parallel oder seriell ausgeführt wurde. (2) Gute Frage, ich denke, eine Warnung könnte protokolliert werden, aber das ist nicht so wichtig.
SebastianRiemer
@Hylianpuffball Ich schreibe ein JSONObject in eine Datei und verwende das Dateisystem von chromes zur Speicherung. Ich denke, der wichtigste Teil davon ist, dass ich den Aufschub innerhalb von fileWriter.onwriteend, fileWriter.onerror usw. auflöse.
SebastianRiemer

Antworten:

121

Was Sie verwenden müssen, ist $ q.all , das eine Reihe von Versprechen zu einem kombiniert, das nur aufgelöst wird, wenn alle Versprechen gelöst sind.

In Ihrem Fall könnten Sie etwas tun wie:

function outerFunction() {

    var defer = $q.defer();
    var promises = [];

    function lastTask(){
        writeSome('finish').then( function(){
            defer.resolve();
        });
    }

    angular.forEach( $scope.testArray, function(value){
        promises.push(writeSome(value));
    });

    $q.all(promises).then(lastTask);

    return defer.promise;
}
Gruff Bunny
quelle
1
Stellt Angualr aus Interesse eine Trennung $q.defer().resolve()wie in jQuery bereit ? Mit anderen Worten, könnten Sie schreiben writeSome('finish').then(defer.resolve);? In diesem Fall wäre der Code etwas kompakter, aber ansonsten identisch.
Rote Beete-Rote Beete
1
Guter Vorschlag. Die 'then'-Funktion übernimmt eine Funktion, die aufgerufen wird, wenn das Versprechen aufgelöst wird. Ja, das Übergeben eines Parameters von defer.resolve funktioniert also. Ich werde die Antwort so lassen, wie sie jetzt ist, da sich dort auch einige Fragen angemeldet haben (die ich aus Gründen der Klarheit weggelassen habe).
Gruff Bunny
Vielen Dank für Ihren Vorschlag @GruffBunny Ich werde es so schnell wie möglich prüfen und Sie wissen lassen!
SebastianRiemer
@GruffBunny Danke für deine Erklärung! Das Versprechen.Push ... und $ q.all war das, wonach ich gesucht habe!
SebastianRiemer
1
@ Jason, verkette die Versprechen: Beispiel .
Michel van Engelen
3

Mit dem neuen ES7 können Sie das gleiche Ergebnis viel einfacher erzielen:

let promises =  angular.forEach( $scope.testArray, function(value){
    writeSome(value);
});

let results = await Promise.all(promises);

console.log(results);
Maurizio In Dänemark
quelle
3
Bist du sicher, dass das so angular.forEach()funktioniert? Wäre let promises = $scope.testArray.map(writeSome);nicht besser?
Roamer-1888
2
Und let results = await Promise.all($scope.testArray.map(writeSome));ist noch kompakter.
Roamer-1888
@ Roamer-1888 zögern Sie nicht zu bearbeiten, wenn es nicht funktioniert. Ich denke, Sie haben Recht, ich habe das nicht vollständig getestet
Maurizio In Dänemark
Ich habe auch in C # warten sehen. Ich denke, beide teilen die gleiche Idee.
Robbie Smith
1

Sie können zusammen verwenden $qund "reduzieren", um die Versprechen zu verketten.

function setAutoJoin() {
    var deferred = $q.defer(), data;
    var array = _.map(data, function(g){
            return g.id;
        });

    function waitTillAllCalls(arr) {
        return arr.reduce(function(deferred, email) {
            return somePromisingFnWhichReturnsDeferredPromise(email);
        }, deferred.resolve('done'));
    }

    waitTillAllCalls(array);

    return deferred.promise;
}
STAHL
quelle
0

Dies funktionierte bei mir mit der ES5-Syntax

function outerFunction(bookings) {

    var allDeferred = $q.defer();
    var promises = [];

    lodash.map(bookings, function(booking) {
        var deferred = $q.defer();

        var query = {
            _id: booking.product[0].id,
            populate: true
        }

        Stamplay.Object("product").get(query)
        .then(function(res) {
            booking.product[0] = res.data[0];
            deferred.resolve(booking)
        })
        .catch(function(err) {
            console.error(err);
            deferred.reject(err);
        });

        promises.push(deferred.promise);
    });

    $q.all(promises)
    .then(function(results) { allDeferred.resolve(results) })
    .catch(function(err) { allDeferred.reject(results) });

    return allDeferred.promise;
}
Ben Cochrane
quelle