3

Asynchronous programming is so much more difficult than synchronous programming.

Using nodeJS, I am trying the following:

for (var i = 0; i < 10; i++) {
    someFunction().then(function() {
        // Do stuff
    });
}

But I want the loop to continue only when the Do Stuff part has completed.

Any idea how this can easily be achieved...?

Thank you!

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
Onestone
  • 609
  • 2
  • 10
  • 23

7 Answers7

5

To expand on Anders' answer, this is how I've handled this in the past with multiple promises that need to be awaited:

var promises = [];

for (var i = 0; i < 10; i++) {
    promises.push(someFunction().then(function() {
            // Do stuff
        })
    );
}

Q.all(promises)
.then(function() {
    // Do all the things!
})
  • It seems that the OP might want to enforce the order in which the promises are kicked off, in other words, starting the second process only when the first has finished. –  Oct 10 '14 at 11:36
  • where does 'Q' come from? – Jon V Dec 30 '20 at 09:54
5

Async programming can be confusing, but most of the confusion can be eliminated if you keep in mind things like callbacks and then will run at a later time, after the code block they're contained in has already finished.

Promise libraries, async module, are all attempts to get more control over a program's flow. Here is a good article explaining different approaches, that really helped me understand things by seeing different alternative solutions to the same problem.

A couple of the other answers mention Q.all(). This works well, if you already have an array of promises.

If you have an array of values, or promises, there is another library that makes this even easier, called bluebird. It has a method called .map() that you can use to start a promise chain with an array.

With this approach you don't need to call the asynchronous, promise-returning function, store the returned promise in an array, then pass that array to Q.all. It saves you some steps.

So, assuming you have an array of just values:

var items = [0,1,2,3,4,5,6,7,8,9];

You can do something like the following:

Promise.map(items, function (item) {
  return performAsyncOperation(item);
}, {concurrency: n})
.then(function(allResults){
  //  'allResults' now contains an array of all
  //  the results of 'performAsyncOperation'
})

Note: For this to work as expected, performAsyncOperation must return a promise

Also note the 3rd argument to Promise.map(): {concurrency: n}. With this option specified, bluebird will only allow n operations to be performed at a time, which can be useful if you have a huge amount of items to process that would overwhelm the system if they were all kicked off at once (network connections, filehandles, etc).

Final note: Here is the bluebird API doc. It's extremely well written with lots of examples, and a great way to explore how promises can help make your life easier.

Hope it helps!!!

aarosil
  • 4,848
  • 3
  • 27
  • 41
2

The pattern I like to use in these kind of situations is to define a function that calls itself once an async operation has completed. Your example would be in the lines of:

var times   = 10;
var current = 0;

(function nextLap() {
    if (current >= times) {
        return callback();
    }

    ++current;

    someFunction()
    .then(function() {
        // do stuff
        nextLap();
    })
    .catch(callback);
})();
Gaston Sanchez
  • 1,181
  • 6
  • 13
1

Use Q.all to wait for all promises to resolve before continuing

Anders Bornholm
  • 1,326
  • 7
  • 18
1

You could chain your promises with reduce:

array.reduce(function(promise, elt) {
    return promise.then(function() { return long_process(elt); });
}, new Promise.resolve());

The result of this expression will be a promise for the sequence of operations having completed. Instead of just invoking the ten asynchronous operations and waiting for them all to finish, this code will wait until the first operation is finished before kicking off the second, if that's important.

1

more simple, use:

forEach with await

Example :

var myarray = [1,2,3];
myarray.forEach(async i => {
    await someFunction().then(function() {
       // Do stuff
    });
});
Julien
  • 3,743
  • 9
  • 38
  • 67
0

I had a similar need where I had to query a paged api until I found a match or iterated through each page. We return the total_count in each call.

const request = require('request-promise');
var total_count;
function findItem(uri, authorization, find_string, start_index, page_size) {        
    return new Promise((resolve, reject) => {
        const options = buildGetOptions(uri, authorization, start_index, page_size);
        request(options)
        .then((res) => {
            const count = JSON.parse(res).total_count;
            if (total_count != count) {                
                total_count = count;
            }
            if (JSON.parse(res).things.some(s => s.name === find_string)) {
                resolve(true);
            } else if (start_index >= total_count) {
                resolve(false);
            } else {
                resolve(findItem(uri, authorization, find_string, start_index + page_size, page_size));
            }
        })
        .catch((err) => {
            reject(err);
        });
    });
}
function buildGetOptions(uri, authorization, start_index, page_size) {
    return {
        method: 'GET',
        uri: uri + `?start_index=${start_index}&page_size=${page_size}`,
        headers: {
            'cache-control': 'no-cache',
            'content-type': 'application/json',
            'Authorization': authorization
        }
    };
}
JimmyV
  • 493
  • 3
  • 12
  • 1
    Avoid the [`Promise` constructor antipattern](https://stackoverflow.com/q/23803743/1048572?What-is-the-promise-construction-antipattern-and-how-to-avoid-it)! – Bergi Mar 19 '19 at 19:39
  • I am new to promises as others have mentioned in that thread. I am curious as to how to solve the issue with a proper pattern as above. I can pull all of the pages down and then search them, but I was unable to determine how to break execution when I found a match and perform the search on each page before moving onto the next. – JimmyV Mar 19 '19 at 20:04
  • Just drop the `new Promise((resolve, reject) => {`, and `return` the results from the `then` callback instead of calling `resolve()`. – Bergi Mar 19 '19 at 20:10
  • @Bergi, thank you. I am starting to do that in my code now. I'll leave this here with the comments to hopefully help another new promise goer that comes up with the same anti-pattern solution. – JimmyV Mar 19 '19 at 20:27