Make angular.forEach wait for promise after going to next object

Problem

I have a list of objects. The objects are passed to a deferred function. I want to call the function with the next object only after the previous call is resolved. Is there any way I can do this?

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

Solution

You can't use .forEach() if you want to wait for a promise. Javascript and promises just don't work that way.

  1. You can chain multiple promises and make the promise infrastructure sequence them.

  2. You can iterate manually and advance the iteration only when the previous promise finishes.

  3. You can use a library like async or Bluebird that will sequence them for you.

There are lots of different alternatives, but .forEach() will not do it for you.


Here's an example of sequencing using chaining of promises with angular promises (assuming objects is an array):

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

And, using standard ES6 promises, this would be:

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

Here's an example of manually sequencing (assuming objects is an array), though this does not report back completion or errors like the above option does:

function run(objects) {
    var cntr = 0;

    function next() {
        if (cntr < objects.length) {
            doSomething(objects[cntr++]).then(next);
        }
    }
    next();
}
Solution courtesy of: jfriend00

Discussion

It worked for me like this. I don't know if it is a right approach but could help to reduce lines

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
});
Discussion courtesy of: ajay-annie

Another solution you would prefer which is very similar to @friend00's solution:

var doSomething = function (index) {
    console.log("Current object: ", objects[index]);
    var defer = $q.defer();
    // Some delay with $timeout for simulating your process
    $timeout(function () {
        if (objects[++index]) {
            defer.resolve(index);
        } else {
            defer.reject();
        }
    }, 3000);

    defer.promise.then(doSomething);
};

doSomething(0);

And here is a working example on jsfiddle: https://jsfiddle.net/ordceoe9/7/

Discussion courtesy of: nerezo

check $q on 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;
}
Discussion courtesy of: bln

Yes you can use angular.forEach to achieve this.

Here is an example (assuming objects is an array):

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

Here is how this can be done using array.reduce, similar to @friend00's answer (assuming objects is an array):

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());
Discussion courtesy of: Bjarni

It might help someone as I tried several of above solution before coming up with my own that actually worked for me (the other ones didn't)

  var sequence;
  objects.forEach(function(item) {
     if(sequence === undefined){
          sequence = doSomethingThatReturnsAPromise(item)
          }else{
          sequence = sequence.then(function(){
               return doSomethingThatReturnsAPromise(item)
                     }); 
                 }
        });
Discussion courtesy of: Lomithrani

The easiest way is to create a function and manually iterate over all the objects in the array after each promise is resolved.

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

Example is also available on my GitHub: https://github.com/pietervw/Deferred-Angular-FOR-Loop-Example

Discussion courtesy of: PeterPan

This recipe can be found in it's original form on Stack Over Flow.