1

In nodeJS, how do I simultaneously call async functions and wait for all callbacks before proceed?

In the example bellow I want main to return all_results only when f1, f2 and f3 are done with the callback()

function main(callback){
  var all_results = [];
  f1(function(results){
    all_results.push(result)
  });

  f2(function(results){
    all_results.push(result)
  });

  f3(function(results){
    all_results.push(result)
  });

  // when all 3 calls are complete:
  callback(all_results)
}


function f1(callback){
  ...
  callback(results);
}

function f2(callback){
  ...
  callback(results);
}

function f3(callback){
  ...
  callback(results);
}

Not using promises.

0

2 Answers 2

5

You cannot return an asynchronously retrieved result in a synchronous way. So the function main cannot return the results. You should stick to the asynchronous method you have chosen. So with callbacks, you should also provide a callback function when invoking main:

function main(callback){
    var all_results = [];

    function collect(results) {
        all_results.push(results);
        // when all 3 calls are complete:
        if (all_results.length === 3) callback(all_results);
    }

    f1(collect);
    f2(collect);
    f3(collect);
}

Call like this:

main(function (all_results) {
    console.log(all_results);
});

As others have stated, promises lead to nicer code in such situations.

How to do it with promises

Let's say you cannot modify functions f1, f2, f3, then you could create promisified versions of them with one simple helper function. Once that is done, the ES6-included method Promise.all will do the rest of the job, so the code becomes quite concise.

Here is a snippet that defines some dummy functions f1, f2, f3 which use setTimeout to get the asynchronous effect:

function promisify(f) {
    return new Promise(f);
}

function main(){
    return Promise.all([f1, f2, f3].map(promisify));
}

main().then(function (results) {
    console.log(results);
});

function f1(callback){
    setTimeout(function () {
        callback('result from f1');
    }, 100);
}

function f2(callback){
    setTimeout(function () {
        callback('result from f2');
    }, 500);
}

function f3(callback){
    setTimeout(function () {
        callback('result from f3');
    }, 200);
}

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

3 Comments

FWIW, you used to be able to with a module called sync, though 1) don't 2) sync doesn't work anymore 3) don't
Why not pass the collect function as the callback rather than the redundant function wrapping the call to collect f1(collect); much more compact and efficient over f1(function(results){collect(results)});
@trincot Superb. I'm oldschool and promises make me have nightmares at night. But I'll keep that in mind
0

Add a boolean variable for each function and only return when all are set:

function main(callback){
  var all_results = [];
  var finished = [false, false, false];
  f1(function(results){
    all_results.push(result)
    finished[0] = true;
  });

  f2(function(results){
    all_results.push(result)
    finished[1] = true;
  });

  f3(function(results){
    all_results.push(result)
    finished[2] = true;
  });

  var t = setInterval(checkFinished,1000);
  function checkFinished(){
      if(finished[0] && finished[1] && finished[2]){
          clearInterval(t)
          callback(all_results)
      }
  }
}

5 Comments

Hmm I thought something like that! But I was afraid it was the wrong way of doing it. Doing the while won't it delay other parts of the code from being executed?
The while loop 'while(!finished[0] || !finished[1] || !finished[2]){' is blocking and will never exit and will simply result in the context having to be reset.
Yes the second method will work but is not a good solution. The accepted answer has the best methods using callbacks and promises.
@Blindman67, absolutely, thats why i've upvoted it, its a far better answer, ta for the heads-up about the while loop blocking
polling on a state variable is invariably the wrong way to do this, not least because it'll cause an artificial delay between the end of the last task and the triggering of the "all jobs done" event.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.