There are some different ways of doing control flow in Node.JS. The "traditional" most forward way of doing this is by using callbacks, but this would lead to something we call "callback hell" or the boomerang effect. You can also use promises or libraries to handle callback hell. Another way of doing control flow (amongst other) in Node.JS is by using streams.
See below for examples of the different control flow techniques.
Example using callbacks:
function prep1(x, cb) { do something; cb(); };
function prep2(x, cb) { do something; cb(); };
function prep3(x, cb) { do something; cb(); };
function mainLoop(x) { };
prep1(x, function () {
prep2(y, function () {
prep3(z, function () {
mainLoop();
});
});
});
// could be shortened to
prep1(x, prep2(y, prep3(z, mainLoop)));
As you can see this could lead to some very ugly code. Also, note that this code is not run in parallel, but in series. See below to see how this would work if you want to run this in parallel where you have some sort of async operations in each of the preparation functions.
Example using parallel callbacks:
// cb() is called inside some async operation
function prep1(x, cb) { do async; cb(y); };
function prep2(x, cb) { do async; cb(y); };
function prep3(x, cb) { do async; cb(y); };
function mainLoop(x) {
// x is an array of values given by the callback in prep1-3
};
var numCalls = 0, result = [];
function maybeCallMainLoop(data) {
if (++numCalls !== 3) {
// Accumulate all responses given by argument y from prep1-3
result.push(data);
return;
}
// run main loop when all functions are called
mainLoop(result);
}
prep1(x, maybeCallMainLoop);
prep2(y, maybeCallMainLoop);
prep3(z, maybeCallMainLoop);
Not really the most clean and robust way of doing this. A better way would like the next example.
A much, much, cleaner way of solving this is by using the async library. More specifically the #parallel method
Example using the #parallel method of the async library
// Signature of the callback: callback(error, value)
function prep1(x, cb) { do something; cb(null, "value"); };
function prep2(x, cb) { do something; cb(null, "value"); };
function prep3(x, cb) { do something; cb(null, "value"); };
function mainLoop(error, x) { };
async.parallel([
prep1,
prep2,
prep3
], mainLoop);
See more info in the async readme
A third way of doing this is by using promises, as you said. You would return a promise from each preparation function and than wait to do the mainLoop.
You can use the Q library which is a A* compliant promise library.
Example using promises with the Q library
var Q = require('q');
// Signature of the callback: callback(error, value)
function prep1(x) {
// do something;
var deferred = Q.defer();
deferred.resolve() // resolve promise
return deferred.promise;
};
function prep2(x) { // same as prep1 };
function prep3(x) { // same as prep1 };
function mainLoop(result) { };
// Q.all returns a promise that is
// resolved when all promises given are resolved
Q.all([
prep1(x),
prep2(y),
prep3(z)
]).then(mainLoop);
See documentation of Q.all
That's some ways of solving it. I'm not really seeing a way of solving this in a pretty way using events - but it would look a lot like the example with parallel callbacks. With a flag indicating the number of preparation functions that is done.
You can see that using either something like async or promises will easily be the most comprehensible and easily readable. If you just want to have the results from "thin" (functions with low cyclomatic complexity), I'd probably recommend using async. If your preparation functions how some complexity and/or the result is used in different ways, promises will be the best approach.
Small end note on streams
In Node.JS you also have something called streams. Streams are, in the most basic way, manageable event emitters with back pressure (a way of stopping the flow of data) functionality. Read a simple analogy in Daddy, what's a stream?.
With streams you can have a source and pipe it through different transformation streams and end the stream with some sort of sink or end point. This is how ever a series instead of parallel, but you can do much more things with streams. Streams have some commonalities with promises.
Example using streams
function prep1(x) { return stream; };
function prep2(x) { return stream; };
function prep3(x) { return stream; };
function mainLoop(result) { };
// Some start. E.g fs.createReadStream("somefile")
someStartStream
.pipe(prep1(x))
.pipe(prep2(y))
.pipe(prep3(z))
.on('end', mainLoop);
Again, this is in a sequence, not parallel. This is just to show that in control flow in Node.JS you can also use streams.
Best of luck!