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

Razvan picture Razvan · Mar 11, 2015 · Viewed 52.1k times · Source

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

Answer

jfriend00 picture jfriend00 · Mar 11, 2015

Before ES2017 and async/await (see below for an option in ES2017), you can't use .forEach() if you want to wait for a promise because promises are not blocking. 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();
}

ES2017

In ES2017, the async/wait feature does allow you to "wait" for a promise to fulfill before continuing the loop iteration when using non-function based loops such as for or 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);
    }
}

The code has to be contained inside an async function and then you can use await to tell the interpreter to wait for the promise to resolve before continuing the loop. Note, while this appears to be "blocking" type behavior, it is not blocking the event loop. Other events in the event loop can still be processed during the await.