1

I have several functions which essentially prep up my app. once these prep functions have finished, i want to run the main loop of my app.

So while the prep functions are async with each other, the main loop only runs when all the prep functions have been resolved.

function prep1(x) { do something };
function prep2(x) { do something };
function prep3(x) { do something };

function mainLoop(x) { only start this function when all 3 prep ones are resolved };

I am basically learning controlflow in nodejs. My question here is how can one achieve what i require, using:

  1. callbacks
  2. events
  3. promises (if a library is being used, the name will be sufficient)
1

2 Answers 2

2

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!

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

7 Comments

Thank you. this is perfect. I can now compare and contrast the 3 code samples that you gave.
regarding events, can we use a status flag?
There are ways to solve this using events, but they wouldn't make too much sense or really be too usable in my opinion. If you really want I could update my answer with a possible solution - how ever I wouldn't recommend using it in any way.
No its fine. I wanted to learn the best way to achieve this result. and your answer allows me to try all 3 approaches on my own.
Notice that the callback example does not run in parallel, but sequentially.
|
1

You can also use a module called nimble.

npm install nimble --save

Then structure your code as follows:

var flow = require('nimble');

flow.series([
     function (callback) {
          function prep1(x) { do something };
          callback();
     },
     function (callback) {
          function prep2(x) { do something };
          callback();
     },
     function(callback) {
          function prep3(x) { do something };
          callback();
     },
     function(callback) {
          function mainLoop(x) { };
          callback();
     }
]);

Comments

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.