Зачекайте, коли всі обіцянки вирішаться


107

Тож у мене ситуація, коли у мене є кілька ланцюжків обіцянок невідомої довжини. Я хочу, щоб деякі дії були запущені, коли всі ланцюги були оброблені. Це навіть можливо? Ось приклад:

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

У цьому прикладі я створив $q.all()обіцянки один, два та три, які вирішаться в якийсь випадковий час. Потім я додаю обіцянки в кінці одного і трьох. Я хочу, allщоб рішення вирішилося, коли всі ланцюги були вирішені. Ось результат, коли я запускаю цей код:

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

Чи є спосіб дочекатися розв’язання ланцюгів?

Відповіді:


161

Я хочу, щоб усі вирішили, коли всі ланцюги були вирішені.

Звичайно, тоді просто передайте обіцянку кожного ланцюга all()замість початкових обіцянок:

$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
Дякую за підтвердження мого найгіршого страху. Тепер я повинен придумати спосіб отримати останню обіцянку хаха.
jensengar

У чому проблема? Ваші ланцюги динамічно побудовані?
Бергі

Саме моя проблема. Я намагаюся динамічно створити ланцюжок обіцянок, тоді я хочу щось зробити, коли ланцюги (и) завершиться.
jensengar

Чи можете ви показати нам свій код (можливо, задайте нове запитання)? Чи є елементи, додані до ланцюжка після того, як Q.allбув виконаний - інакше це має бути тривіально?
Бергі

Я хотів би показати вам код ... але я ще не закінчив його писати, проте я зроблю все можливе, щоб це пояснити. У мене є список «дій», які потрібно зробити. Ці дії можуть мати будь-який кількість рівнів подій, пов'язаних з ними. Я хочу вміти щось робити, коли всі дії та їх відсмоктування завершені. Ймовірно, буде декілька $q.alls, однак, як тільки я розпочну процес вирішення, нові дії / обіцянки не будуть зв'язані.
jensengar

16

Загальноприйнятий відповідь є правильним. Я хотів би навести приклад, щоб трохи розробити його тим, хто не знайомий promise.

Приклад:

У моєму прикладі мені потрібно замінити srcатрибути imgтегів на різні дзеркальні URL-адреси, якщо вони доступні, перш ніж надавати вміст.

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

Пояснення:

З документів AngularJS :

thenметод:

потім (successCallback, errorCallback, notifyCallback) - незалежно від того, коли обіцянка була або буде вирішена або відхилена, тоді асинхронно викликає один із зворотних викликів успіху чи помилки, як тільки результат буде доступний. Зворотні виклики викликаються одним аргументом : результат або причина відхилення.

$ q.all (обіцянки)

Поєднує декілька обіцянок в одну обіцянку, яка вирішується, коли всі вхідні обіцянки вирішені.

promisesПари можуть бути масивом обіцянок.

Про bind(), Більше інформації тут: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind


thenМетод $q.allпередбачений масив повернутих обіцянок, так що ви можете обходити цей масив і виклик thenпо відношенню до кожного елементу масиву, на відміну від виклику , thenколи ви додаєте обіцянку promise_array.
прізвисько

4

Нещодавно була ця проблема, але з невизнаною кількістю обіцянок. Вирішено за допомогою 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;
}

Це не jQuery.map (), але Array.prototype.map () ( developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… ), але такий підхід працює.
Анастасія

0

Є спосіб. $q.all(...

Ви можете перевірити наведені нижче продукти:

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

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


Це вимагає, щоб я знав довжину мого ланцюга, правда? Я маю на увазі, якби у мене була обіцянка завдовжки 10, я повинен був би зробити це $q.all([p1.then(..).then(...).then(...).then(...) ...]);правильно?
jensengar

0

Ви можете використовувати функцію "очікувати" в "функції асинхронізації" .

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

ПРИМІТКА. Я не на 100% впевнений, що ви можете викликати функцію асинхронізації з функції несинхронізації та мати правильні результати.

Це сказало, що це ніколи не буде використовуватися на веб-сайті. Але для тестування навантаження / інтеграції ... можливо.

Приклад коду:

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.