Warten Sie, bis alle Versprechen gelöst sind

107

Ich habe also eine Situation, in der ich mehrere Versprechensketten unbekannter Länge habe. Ich möchte, dass eine Aktion ausgeführt wird, wenn alle KETTEN verarbeitet wurden. Ist das überhaupt möglich? Hier ist ein Beispiel:

app.controller('MainCtrl', function($scope, $q, $timeout) {
    var one = $q.defer();
    var two = $q.defer();
    var three = $q.defer();

    var all = $q.all([one.promise, two.promise, three.promise]);
    all.then(allSuccess);

    function success(data) {
        console.log(data);
        return data + "Chained";
    }

    function allSuccess(){
        console.log("ALL PROMISES RESOLVED")
    }

    one.promise.then(success).then(success);
    two.promise.then(success);
    three.promise.then(success).then(success).then(success);

    $timeout(function () {
        one.resolve("one done");
    }, Math.random() * 1000);

    $timeout(function () {
        two.resolve("two done");
    }, Math.random() * 1000);

    $timeout(function () {
        three.resolve("three done");
    }, Math.random() * 1000);
});

In diesem Beispiel habe ich ein $q.all()für Versprechen eins, zwei und drei eingerichtet, die zu einem zufälligen Zeitpunkt aufgelöst werden. Ich füge dann Versprechen an die Enden von eins und drei hinzu. Ich möchte, dass das aufgelöst wird all, wenn alle Ketten aufgelöst wurden. Hier ist die Ausgabe, wenn ich diesen Code ausführe:

one done 
one doneChained
two done
three done
ALL PROMISES RESOLVED
three doneChained
three doneChainedChained 

Gibt es eine Möglichkeit zu warten, bis sich die Ketten aufgelöst haben?

Jensengar
quelle

Antworten:

161

Ich möchte, dass alles aufgelöst wird, wenn alle Ketten aufgelöst wurden.

Sicher, dann geben Sie einfach das Versprechen jeder Kette an die all()statt der ursprünglichen Versprechen weiter:

$q.all([one.promise, two.promise, three.promise]).then(function() {
    console.log("ALL INITIAL PROMISES RESOLVED");
});

var onechain   = one.promise.then(success).then(success),
    twochain   = two.promise.then(success),
    threechain = three.promise.then(success).then(success).then(success);

$q.all([onechain, twochain, threechain]).then(function() {
    console.log("ALL PROMISES RESOLVED");
});
Bergi
quelle
2
Vielen Dank, dass Sie meine schlimmste Angst bestätigt haben. Jetzt muss ich einen Weg finden, um das letzte Versprechen zu bekommen lol.
Jensengar
Was ist das Problem damit? Sind Ihre Ketten dynamisch aufgebaut?
Bergi
Genau mein Problem. Ich versuche dynamisch eine Versprechenskette zu erstellen, dann möchte ich etwas tun, wenn die Kette (n) abgeschlossen sind.
Jensengar
Können Sie uns Ihren Code zeigen (vielleicht eine neue Frage stellen)? Gibt es Elemente, die nach Q.allder Ausführung an die Kette angehängt wurden - ansonsten sollte es trivial sein?
Bergi
Ich würde Ihnen gerne den Code zeigen ... aber ich habe ihn noch nicht fertig geschrieben, aber ich werde mein Bestes geben, um ihn zu erklären. Ich habe eine Liste von "Aktionen", die ausgeführt werden müssen. Diesen Aktionen können beliebig viele Ebenen von Unteraktionen zugeordnet sein. Ich möchte in der Lage sein, etwas zu tun, wenn alle Aktionen und ihre Unteraktionen abgeschlossen sind. Es wird wahrscheinlich mehrere $q.alls geben, aber sobald ich den Lösungsprozess starte, werden keine neuen Aktionen / Versprechen verkettet.
Jensengar
16

Die akzeptierte Antwort ist richtig. Ich möchte ein Beispiel geben, um es denjenigen näher zu bringen, die nicht damit vertraut sind promise.

Beispiel:

In meinem Beispiel muss ich die srcAttribute von imgTags durch verschiedene Spiegel-URLs ersetzen, falls verfügbar, bevor der Inhalt gerendert wird.

var img_tags = content.querySelectorAll('img');

function checkMirrorAvailability(url) {

    // blah blah 

    return promise;
}

function changeSrc(success, y, response) {
    if (success === true) {
        img_tags[y].setAttribute('src', response.mirror_url);
    } 
    else {
        console.log('No mirrors for: ' + img_tags[y].getAttribute('src'));
    }
}

var promise_array = [];

for (var y = 0; y < img_tags.length; y++) {
    var img_src = img_tags[y].getAttribute('src');

    promise_array.push(
        checkMirrorAvailability(img_src)
        .then(

            // a callback function only accept ONE argument. 
            // Here, we use  `.bind` to pass additional arguments to the
            // callback function (changeSrc).

            // successCallback
            changeSrc.bind(null, true, y),
            // errorCallback
            changeSrc.bind(null, false, y)
        )
    );
}

$q.all(promise_array)
.then(
    function() {
        console.log('all promises have returned with either success or failure!');
        render(content);
    }
    // We don't need an errorCallback function here, because above we handled
    // all errors.
);

Erläuterung:

Aus AngularJS- Dokumenten :

Die thenMethode:

then (successCallback, errorCallback, notifyCallback) - unabhängig davon, wann das Versprechen gelöst oder abgelehnt wurde oder wird, ruft dann einen der Erfolgs- oder Fehlerrückrufe asynchron auf, sobald das Ergebnis verfügbar ist. Die Rückrufe werden mit einem einzigen Argument aufgerufen : dem Ergebnis oder dem Ablehnungsgrund.

$ q.all (verspricht)

Kombiniert mehrere Versprechen zu einem einzigen Versprechen, das aufgelöst wird, wenn alle Eingabeversprechen gelöst sind.

Der promisesParameter kann eine Reihe von Versprechungen sein.

Über bind()Mehr Infos hier: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind

Hieu
quelle
Die thenMethode von $q.allwird mit einem Array der zurückgegebenen Versprechen bereitgestellt, sodass Sie dieses Array in einer Schleife ausführen und thenjedes Element im Array aufrufen können, anstatt thenbeim Hinzufügen des Versprechens aufzurufen promise_array.
Nick
4

Hatte kürzlich dieses Problem aber mit unbekannter Anzahl von Versprechungen. Mit jQuery.map () gelöst .

function methodThatChainsPromises(args) {

    //var args = [
    //    'myArg1',
    //    'myArg2',
    //    'myArg3',
    //];

    var deferred = $q.defer();
    var chain = args.map(methodThatTakeArgAndReturnsPromise);

    $q.all(chain)
    .then(function () {
        $log.debug('All promises have been resolved.');
        deferred.resolve();
    })
    .catch(function () {
        $log.debug('One or more promises failed.');
        deferred.reject();
    });

    return deferred.promise;
}
SoniCue
quelle
Es ist nicht jQuery.map (), sondern Array.prototype.map () ( developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… ), aber dieser Ansatz funktioniert.
Anastasia
0

Da ist ein Weg. $q.all(...

Sie können die folgenden Dinge überprüfen:

http://jsfiddle.net/ThomasBurleson/QqKuk/

http://denisonluz.com/blog/index.php/2013/10/06/angularjs-returning-multiple-promises-at-once-with-q-all/

Nikola Yovchev
quelle
Dafür muss ich allerdings die Länge meiner Kette kennen, oder? Ich meine, wenn ich ein Versprechen von Länge 10 hätte, müsste ich es $q.all([p1.then(..).then(...).then(...).then(...) ...]);richtig machen?
Jensengar
0

Sie können "warten" in einer "asynchronen Funktion" verwenden .

app.controller('MainCtrl', async function($scope, $q, $timeout) {
  ...
  var all = await $q.all([one.promise, two.promise, three.promise]); 
  ...
}

HINWEIS: Ich bin nicht 100% sicher, ob Sie eine asynchrone Funktion von einer nicht asynchronen Funktion aus aufrufen und die richtigen Ergebnisse erzielen können.

Das heißt, dies würde niemals auf einer Website verwendet werden. Aber für Lasttests / Integrationstests ... vielleicht.

Beispielcode:

async function waitForIt(printMe) {
  console.log(printMe);
  console.log("..."+await req());
  console.log("Legendary!")
}

function req() {
  
  var promise = new Promise(resolve => {
    setTimeout(() => {
      resolve("DARY!");
    }, 2000);
    
  });

    return promise;
}

waitForIt("Legen-Wait For It");

Flavouski
quelle