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?

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

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


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

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

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

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.