Сделайте angular.forEach ждать обещания после перехода к следующему объекту

У меня есть список объектов. Объекты передаются на отложенную функцию. Я хочу вызвать функцию со следующим объектом только после разрешения предыдущего вызова. Есть ли способ сделать это?

angular.forEach(objects, function (object) {
    // wait for this to resolve and after that move to next object
    doSomething(object);
});

Ответ 1

До ES2017 и async/await (см. ниже для опции в ES2017) вы не можете использовать .forEach(), если вы хотите дождаться обещания, потому что promises не блокируют. Javascript и promises просто не работают.

  • Вы можете связать несколько promises и сделать им инфраструктуру последовательности обещаний.

  • Вы можете выполнять итерацию вручную и продвигать итерацию только тогда, когда заканчивается предыдущее обещание.

  • Вы можете использовать библиотеку, такую ​​как async или Bluebird, которая будет их упорядочивать для вас.

Существует много разных альтернатив, но .forEach() не сделает это за вас.


Здесь пример последовательности с использованием цепочки promises с angular promises (при условии, что objects является массивом):

objects.reduce(function(p, val) {
    return p.then(function() {
        return doSomething(val);
    });
}, $q.when(true)).then(function(finalResult) {
    // done here
}, function(err) {
    // error here
});

И, используя стандартный ES6 promises, это будет:

objects.reduce(function(p, val) {
    return p.then(function() {
        return doSomething(val);
    });
}, Promise.resolve()).then(function(finalResult) {
    // done here
}, function(err) {
    // error here
});

Здесь пример ручного секвенирования (предполагается, что objects - это массив), хотя это не сообщает о завершении завершения или ошибки, подобные описанному выше:

function run(objects) {
    var cntr = 0;

    function next() {
        if (cntr < objects.length) {
            doSomething(objects[cntr++]).then(next);
        }
    }
    next();
}

ES2017

В ES2017 функция async/wait позволяет вам "ждать" обещания выполнить до продолжения итерации цикла при использовании не-функциональных циклов, таких как for или while:

async function someFunc() {
    for (object of objects) {
        // wait for this to resolve and after that move to next object
        let result = await doSomething(object);
    }
}

Код должен содержаться внутри функции async, а затем вы можете использовать await, чтобы заставить интерпретатора ждать выполнения обещания до продолжения цикла. Обратите внимание, что, хотя это похоже на поведение типа "блокировка", оно не блокирует цикл события. Другие события в цикле событий все еще могут обрабатываться во время await.

Ответ 2

Да, вы можете использовать angular.forEach для достижения этого.

Вот пример (предполагая, что objects - массив):

// Define the initial promise
var sequence = $q.defer();
sequence.resolve();
sequence = sequence.promise;

angular.forEach(objects, function(val,key){
    sequence = sequence.then(function() {
        return doSomething(val);
    });
});

Вот как это можно сделать, используя array.reduce, аналогично @friend00 answer (предполагается, что objects - массив):

objects.reduce(function(p, val) {
    // The initial promise object
    if(p.then === undefined) {
        p.resolve(); 
        p = p.promise;
    }
    return p.then(function() {
        return doSomething(val);
    });
}, $q.defer());

Ответ 3

проверить $q на angular:

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

Ответ 4

Самый простой способ - создать функцию и вручную перебрать все объекты в массиве после каждого обещания.

var delayedFORLoop = function (array) {
    var defer = $q.defer();

    var loop = function (count) {
        var item = array[count];

        // Example of a promise to wait for
        myService.DoCalculation(item).then(function (response) {

        }).finally(function () {
          // Resolve or continue with loop
            if (count === array.length) {
                defer.resolve();
            } else {
                loop(++count);
            }
        });
    }

    loop(0); // Start loop
    return defer.promise;
}

// To use:
delayedFORLoop(array).then(function(response) {
    // Do something
});

Пример также доступен на моем GitHub: https://github.com/pietervw/Deferred-Angular-FOR-Loop-Example

Ответ 5

Это может помочь кому-то, как я попробовал несколько вышеупомянутых решений, прежде чем придумать свой собственный, который действительно работал у меня (другие не делали этого)

  var sequence;
  objects.forEach(function(item) {
     if(sequence === undefined){
          sequence = doSomethingThatReturnsAPromise(item)
          }else{
          sequence = sequence.then(function(){
               return doSomethingThatReturnsAPromise(item)
                     }); 
                 }
        });

Ответ 6

Это сработало для меня так. Я не знаю, подходит ли это подход, но может помочь уменьшить количество строк.

function myFun(){
     var deffer = $q.defer();
     angular.forEach(array,function(a,i) { 
          Service.method(a.id).then(function(res) { 
               console.log(res); 
               if(i == array.length-1) { 
                      deffer.resolve(res); 
               } 
          }); 
     });
     return deffer.promise;
}

myFun().then(function(res){
     //res here
});