angular $ q, Як прив’язати кілька обіцянок всередині і після циклу for


75

Я хочу мати цикл for, який викликає асинхронні функції на кожній ітерації.

Після циклу for я хочу виконати ще один блок коду, але не раніше, ніж всі попередні виклики у циклі for будуть вирішені.

Моя проблема на даний момент полягає в тому, що або код-блок після циклу for виконується до того, як всі асинхронні виклики закінчаться, або він взагалі не виконується.

Частина коду з циклом FOR та блоком коду після нього (повний код див. У скрипті ):

[..]
function outerFunction($q, $scope) {
    var defer = $q.defer();    
    readSome($q,$scope).then(function() {
        var promise = writeSome($q, $scope.testArray[0])
        for (var i=1; i < $scope.testArray.length; i++) {
             promise = promise.then(
                 angular.bind(null, writeSome, $q, $scope.testArray[i])
             );                                  
        } 
        // this must not be called before all calls in for-loop have finished
        promise = promise.then(function() {
            return writeSome($q, "finish").then(function() {
                console.log("resolve");
                // resolving here after everything has been done, yey!
                defer.resolve();
            });   
        });        
    });   

    return defer.promise;
}

Я створив jsFiddle, який можна знайти тут http://jsfiddle.net/riemersebastian/B43u6/3/ .

На даний момент здається, що порядок виконання чудовий (див. Висновок консолі).

Я припускаю, що це просто тому, що кожен виклик функції негайно повертається, не виконуючи жодної реальної роботи. Я намагався затримати defer.resolve за допомогою setTimeout, але не вдалося (тобто останній блок коду ніколи не виконувався). Ви можете побачити це в закомментованому блоці в скрипці.

Коли я використовую реальні функції, які записують у файл і читають з файлу, останній блок коду виконується до завершення останньої операції запису, що не те, що я хочу.

Звичайно, помилка може бути в одній із цих функцій читання / запису, але я хотів би переконатися, що в коді, який я розмістив тут, немає нічого поганого.


(1) Про функції, які ви викликаєте зсередини циклу: чи повинні вони виконуватися послідовно, чи вони паралельні, все ще вимагаючи запуску останнього блоку після завершення всіх ? І: (2) Що має статися, якщо один із них призведе до помилки?
Нікос Параскевопулос

Якщо ви використовуєте функцію запису, вони часто також є асинхронними, тому дуже можливо, що все працює "за призначенням"; тобто кутовий починає всі записи (що займає частку часу), але самі записи займають багато часу. Що ви пишете, і який API ви використовуєте?
Hylianpuffball

@NikosParaskevopoulos (1) Мені насправді байдуже, працюють вони паралельно чи послідовно, їх можна запускати паралельно, оскільки вони не залежать один від одного. Наразі кожна внутрішня функція повертає обіцянку та вирішує в кінці операції, тобто вона виконується послідовно. Зрозуміло, остання операція завжди повинна бути останньою, незалежно від того, паралельна чи послідовна. (2) Гарне запитання, я думаю, якесь попередження може бути записано, але це не так важливо.
SebastianRiemer

@Hylianpuffball Я пишу файл JSONObject і використовую файлову систему chromes для зберігання. Я думаю, найважливіша частина цього полягає в тому, що я вирішую відстрочку в файлах fileWriter.onwriteend, fileWriter.onerror тощо
SebastianRiemer

Відповіді:


121

Вам потрібно використовувати $ q.all, який поєднує ряд обіцянок в одне, яке вирішується лише тоді, коли всі обіцянки вирішені.

У вашому випадку ви можете зробити щось на зразок:

function outerFunction() {

    var defer = $q.defer();
    var promises = [];

    function lastTask(){
        writeSome('finish').then( function(){
            defer.resolve();
        });
    }

    angular.forEach( $scope.testArray, function(value){
        promises.push(writeSome(value));
    });

    $q.all(promises).then(lastTask);

    return defer.promise;
}

1
З-за інтересу, чи передбачає Angualr $q.defer().resolve()бути відокремленим, як у jQuery? Іншими словами, чи не могли б ви написати writeSome('finish').then(defer.resolve);? Якщо так, то код буде трохи компактнішим, але інакше ідентичним.
Буряк-Буряк

1
Гарна пропозиція. Функція 'then' приймає функцію, яка буде викликана, коли обіцянка буде вирішена, тому так, передача параметра defer.resolve буде працювати. Я залишу відповідь такою, якою вона є зараз, оскільки запитання також мало вхід в систему (яку я опустив для ясності).
Gruff Bunny

Дякую за пропозицію @GruffBunny, я розгляну це якомога швидше і повідомлю вас!
SebastianRiemer

@GruffBunny Дякую за пояснення! The promises.push ... і $ q.all - це те, що я шукав!
SebastianRiemer

1
@ Джейсон, ланцюжок обіцянок: приклад .
Мішель ван Енгелен

3

З новим ES7 ви можете отримати той самий результат набагато простіше:

let promises =  angular.forEach( $scope.testArray, function(value){
    writeSome(value);
});

let results = await Promise.all(promises);

console.log(results);

3
Ви впевнені, що так angular.forEach()працює? Буде let promises = $scope.testArray.map(writeSome);чи не краще?
Роумер-1888

2
І let results = await Promise.all($scope.testArray.map(writeSome));ще більш компактний.
Roamer-1888

@ Roamer-1888 сміливо редагуйте, якщо це не працює. Думаю, ви маєте рацію, я цього ще не повністю перевірив
Мауріціо У Данії

Я бачив await і в C #. Думаю, вони обидва поділяють одну ідею.
Роббі Сміт

1

Ви можете використовувати $qта «зменшувати» разом, щоб об’єднати обіцянки.

function setAutoJoin() {
    var deferred = $q.defer(), data;
    var array = _.map(data, function(g){
            return g.id;
        });

    function waitTillAllCalls(arr) {
        return arr.reduce(function(deferred, email) {
            return somePromisingFnWhichReturnsDeferredPromise(email);
        }, deferred.resolve('done'));
    }

    waitTillAllCalls(array);

    return deferred.promise;
}

0

Це спрацювало для мене, використовуючи синтаксис ES5

function outerFunction(bookings) {

    var allDeferred = $q.defer();
    var promises = [];

    lodash.map(bookings, function(booking) {
        var deferred = $q.defer();

        var query = {
            _id: booking.product[0].id,
            populate: true
        }

        Stamplay.Object("product").get(query)
        .then(function(res) {
            booking.product[0] = res.data[0];
            deferred.resolve(booking)
        })
        .catch(function(err) {
            console.error(err);
            deferred.reject(err);
        });

        promises.push(deferred.promise);
    });

    $q.all(promises)
    .then(function(results) { allDeferred.resolve(results) })
    .catch(function(err) { allDeferred.reject(results) });

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