Подождите, пока все обещания разрешатся

Итак, у меня есть ситуация, когда у меня есть несколько цепочек обещаний неизвестной длины. Я хочу, чтобы какое-то действие выполнялось, когда все ЦЕПИ были обработаны. Это вообще возможно? Вот пример:

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 

Есть ли способ дождаться разрешения цепей?


person jensengar    schedule 13.02.2014    source источник


Ответы (5)


Я хочу, чтобы все разрешилось, когда все цепи будут разрешены.

Конечно, тогда просто передайте обещание каждой цепочки в 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");
});
person Bergi    schedule 13.02.2014
comment
Спасибо, что подтвердили мой худший страх. Теперь я должен придумать способ получить последнее обещание, лол. - person jensengar; 13.02.2014
comment
Что с этим не так? Ваши цепочки построены динамически? - person Bergi; 13.02.2014
comment
Собственно моя проблема. Я пытаюсь динамически создать цепочку обещаний, а затем хочу что-то сделать, когда цепочка (цепочки) завершится. - person jensengar; 13.02.2014
comment
Можете ли вы показать нам свой код (может быть, зададите новый вопрос)? Добавляются ли элементы в цепочку после выполнения Q.all - иначе это должно быть тривиально? - person Bergi; 13.02.2014
comment
Я хотел бы показать вам код ... но я еще не закончил его писать, однако я сделаю все возможное, чтобы объяснить его. У меня есть список действий, которые нужно сделать. С этими действиями может быть связано любое количество уровней вложенных действий. Я хочу иметь возможность что-то делать, когда все действия и их подоперации завершены. Вероятно, будет несколько $q.alls, однако, как только я начну процесс разрешения, новые действия / обещания не будут связаны. - person jensengar; 13.02.2014
comment
Да, вы бы рекурсивно спускались по дереву действий с несколькими all() вызовами дополнительных действий на каждом уровне - тогда вам нужно только вызвать act(rootAction).then(…) - person Bergi; 13.02.2014
comment
Точно, я просто еще не зашел так далеко и не понял, как правильно построить all() для каждого уровня. - person jensengar; 13.02.2014
comment
s / каждый уровень / каждый узел / - извините, если это вызвало некоторую путаницу. - person Bergi; 13.02.2014

принятый ответ правильный. Я хотел бы привести пример, чтобы немного прояснить его для тех, кто не знаком с 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:

then (successCallback, errorCallback, notifyCallback) - независимо от того, когда обещание было или будет выполнено или отклонено, затем асинхронно вызывает один из обратных вызовов успеха или ошибки, как только становится доступен результат. Обратные вызовы вызываются с помощью единственного аргумента: результата или причины отклонения.

$ q.all (обещает)

Объединяет несколько обещаний в одно обещание, которое разрешается, когда разрешены все входные обещания.

Параметр promises может быть массивом обещаний.

О bind(), подробнее здесь: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind

person Hieu    schedule 26.02.2015
comment
Метод then для $q.all предоставляет массив возвращенных обещаний, поэтому вы можете зациклить этот массив и вызвать then для каждого элемента в массиве, в отличие от вызова then, когда вы добавляете обещание в promise_array. - person nick; 19.01.2016

Недавно возникла эта проблема, но с неизвестным количеством обещаний. Решено с помощью 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;
}
person SoniCue    schedule 18.11.2015
comment
Это не jQuery.map (), а Array.prototype.map () (developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/), но этот подход работает. - person Anastasia; 07.02.2016

Есть выход. $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/

person Nikola Yovchev    schedule 13.02.2014
comment
Для этого мне нужно знать длину моей цепочки, верно? Я имею в виду, что если бы у меня было обещание длиной 10, я бы сделал $q.all([p1.then(..).then(...).then(...).then(...) ...]);, верно? - person jensengar; 13.02.2014

Вы можете использовать "await" в "асинхронной функции".

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

person Flavouski    schedule 28.02.2018