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!

7 Answers 7

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!!!

Sign up to request clarification or add additional context in comments.

3 Comments

{concurrency: n} : what does it means?
With this option specified, bluebird will only allow n operations to be performed at a time
Use Promise.each if you want to process the collection sequentially.
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! }) 

2 Comments

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.
where does 'Q' come from?
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); })(); 

Comments

1

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

Comments

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.

Comments

1

more simple, use:

forEach with await

Example :

var myarray = [1,2,3]; myarray.forEach(async i => { await someFunction().then(function() { // Do stuff }); }); 

Comments

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

4 Comments

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.
Just drop the new Promise((resolve, reject) => {, and return the results from the then callback instead of calling resolve().
@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.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.