Poczekaj, aż wszystkie obietnice się spełnią

107

Mam więc sytuację, w której mam wiele łańcuchów obietnic o nieznanej długości. Chcę, aby jakaś akcja została uruchomiona po przetworzeniu wszystkich ŁAŃCUCHÓW. Czy to w ogóle możliwe? Oto przykład:

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

W tym przykładzie ustawiłem $q.all()dla obietnic 1, 2 i 3, które zostaną rozwiązane w jakimś przypadkowym czasie. Następnie dodaję obietnice na końcach pierwszego i trzeciego. Chcę, allaby rozwiązano, gdy wszystkie łańcuchy zostaną rozwiązane. Oto wynik po uruchomieniu tego kodu:

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

Czy jest sposób, aby poczekać na rozwiązanie łańcuchów?

jensengar
źródło

Odpowiedzi:

161

Chcę, żeby wszystko rozwiązało się, gdy wszystkie łańcuchy zostaną rozwiązane.

Jasne, po prostu przekaż obietnicę każdego łańcucha do all()zamiast początkowych obietnic:

$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
źródło
2
Dzięki za potwierdzenie mojego najgorszego strachu. Teraz muszę wymyślić sposób, aby uzyskać ostatnią obietnicę lol.
jensengar
Jaki jest z tym problem? Czy Twoje łańcuchy są zbudowane dynamicznie?
Bergi
Dokładnie mój problem. Próbuję dynamicznie utworzyć łańcuch obietnic, a następnie chcę coś zrobić, gdy łańcuch (y) zostaną ukończone.
jensengar
Czy możesz pokazać nam swój kod (może zadać nowe pytanie)? Czy są jakieś elementy dołączone do łańcucha po Q.allwykonaniu - w przeciwnym razie powinno to być trywialne?
Bergi
Bardzo chciałbym pokazać Ci kod ... ale jeszcze go nie skończyłem pisać, ale postaram się to wyjaśnić. Mam listę „działań”, które należy wykonać. Akcje te mogą mieć dowolną liczbę poziomów działań podrzędnych z nimi związanych. Chcę móc coś zrobić, gdy wszystkie działania i ich poddziałania są zakończone. Prawdopodobnie będzie ich wiele $q.all, jednak gdy rozpocznę proces rozwiązywania problemów, żadne nowe działania / obietnice nie zostaną połączone.
jensengar
16

Odpowiedź akceptowana jest poprawna. Chciałbym podać przykład, aby rozwinąć go nieco tym, którzy nie są zaznajomieni promise.

Przykład:

W moim przykładzie przed renderowaniem treści muszę zamienić srcatrybuty imgtagów na inne lustrzane adresy URL, jeśli są dostępne.

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

Wyjaśnienie:

Z dokumentacji AngularJS :

thenMetoda:

then (successCallback, errorCallback, notifyCallback) - niezależnie od tego, kiedy obietnica została lub zostanie rozwiązana lub odrzucona, wywołuje asynchroniczne wywołania zwrotne sukcesu lub błędu, gdy tylko wynik będzie dostępny. Wywołania zwrotne są wywoływane z jednym argumentem : wynikiem lub powodem odrzucenia.

$ q.all (obietnice)

Łączy wiele obietnic w jedną obietnicę, która jest rozpatrywana po rozwiązaniu wszystkich obietnic wejściowych.

Parametr promisesmoże być tablicą obietnic.

Informacje bind(), więcej informacji tutaj: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind

Hieu
źródło
thenMetoda $q.alljest tablicę zwróconych obietnic, więc można pętli że tablica i wezwanie thenna każdej pozycji w tablicy, w przeciwieństwie do wywoływania thenpodczas dodawania obietnicę promise_array.
nick
4

Ostatnio miałem ten problem, ale z nieznaną liczbą obietnic. Rozwiązany za pomocą jQuery.map () .

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
źródło
To nie jest jQuery.map (), ale Array.prototype.map () ( developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/ ... ), ale to podejście działa.
Anastasia
0

Jest sposób. $q.all(...

Możesz sprawdzić poniższe rzeczy:

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
źródło
To wymaga jednak znajomości długości mojego łańcucha, prawda? Chodzi mi o to, że gdybym miał obietnicę o długości 10, musiałbym zrobić $q.all([p1.then(..).then(...).then(...).then(...) ...]);dobrze?
jensengar
0

Możesz użyć „await” w „funkcji asynchronicznej” .

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

UWAGA: Nie jestem w 100% pewien, czy możesz wywołać funkcję asynchroniczną z funkcji innej niż asynchroniczna i uzyskać właściwe wyniki.

To powiedziawszy, nigdy nie będzie używane w witrynie. Ale w przypadku testów obciążeniowych / testów integracji ... może.

Przykładowy kod:

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
źródło