1

For example, I want to write a test case, which need to track the status of a series calls.

I can get something like this:

async_fun(function () {
    // Do something ...

    async_fun(function () {
        // Do something ...

        async_fun(function () {
            // Do something ...
            // ...
        });
    });
});

async_fun();

When I need to run a large loop, I can make a tail recursion like below:

function helper (mount) {
    async_fun(function (){
        if (mount) return;

        // Do something ...

        helper(mount--);
    });
}

helper(10000);

But, I heard that V8 engine has no optimization for the tail call, so it may eat up the RAM. Is there a better design pattern to achieve this task?

PS: No third lib please. I want a native solution.

Community
  • 1
  • 1
Yad Smood
  • 2,752
  • 2
  • 24
  • 37

1 Answers1

2

For your second example I recommend using events. If you use registered events, along with a global(or closure) counter, it will keep the call stack from growing, but achieve the same logic. Assume the countDown method does some asynchronous work, just pass this async work a callback that emits the next event in line.

var events = require("events");

function recursiveCountDown(someVariable) {
    var counter = someVariable;
    var eventEmitter = new events.EventEmitter();//create a new event object, so we can have lots of these running potentially without interfering with one another!
    var eventName = 'count';

    function countDown()  {
        someVariable--;
        console.log(someVariable);
        if(someVariable) eventEmitter.emit(eventName);
    }

    eventEmitter.on(eventName, countDown);

    eventEmitter.emit(eventName);
}

recursiveCountDown(1000);

For your first issue there are several flow control libraries available. To be honest, you have organized this in one of the more nasty ways. There are some organizational things you can do to make this "better", but they all end up looking only marginally better. There is no way to avoid this flow of logic, and from my point of view, I prefer seeing how things get executed. But, this is just an opinion. You can look into some of the flow control libraries, ASYNC seems to be the standard. Basically, what it allows, is for you to present your functions as if they were executing in line, despite the fact that internally they are being wrapped and executed as successive callbacks exactly like you have presented above. I prefer the following idiom:

function doABunchOfAsyncWorkInSeries(arg, callbackToMainEventLoop) {
    var sharedByAll = 'OUTPUT: '
    setTimeout(function(){
        console.log(sharedByAll + arg);
        asyncFun2('a different string');
    }, 1000);

    function asyncFun2(arg2) {
        setTimeout(function() {
            console.log(sharedByAll + arg2);
            asyncFun3('final string');
        }, 2000);
    }

    function asyncFun3(arg3) {
        setTimeout(function(){
            console.log(sharedByAll +arg3);
            callbackToMainEventLoop('FINISHED');
        }, 3000);
    }
}

doABunchOfAsyncWorkInSeries('first string', function(arg) {
    console.log('The program is finished now. :' + arg);
});

Notice that the flow of logic is essentialy the same, but the functions are written in series. So it is clear that one is executing after the other, despite the fact that the doSomeWork.... functions can be asyncrhonous without effecting logic flow. In your example you do the same thing, but each consecutive function contains another function within its closure... There's no reason to do it this way. This just looks a little cleaner. Again, if you don't mind libraries doing things like this for you, to simplify your syntax, look into Async. But internally, this is what Async is doing. And I honestly like this syntax better.

MobA11y
  • 18,425
  • 3
  • 49
  • 76
  • The `args` in your second example will only work for the first function call – Mulan Jul 29 '13 at 15:00
  • You are misunderstanding. The args variable is not meant to represent the same thing, this is just a generic symbol for "whatever you need to place here". The args passed into the second function is whatever comes from whatever async work you end up doing, be it fs, http, etc. If you want the same args available to all callbacks just create that variable in the closure. – MobA11y Jul 29 '13 at 15:02
  • I renamed them for clarity, but functionally things have not changed. Actually, if you were working with the same 'args' variable, you could also emit the variable completely and just rely on the args defined at the closure level. But, then it wouldn't be as useful of an example :) – MobA11y Jul 29 '13 at 15:04
  • I have read some of the third libs, that's why I want a better solution to solve it from the bottom. – Yad Smood Jul 29 '13 at 15:10
  • @YAD: That comment makes 0 sense to me... Third libs? Solve it from the bottom? I only recommended looking into a third party library, my example code is native. Nothing is going to keep the flow of logic from being function(callback){function(callback)} etc. Only different ways of presenting this logic flow. – MobA11y Jul 29 '13 at 15:12
  • @ChrisCM You are right, I appreciate your first solution, and I need a while to understand it and test it. The second one may not what I wanted. But still, thanks a lot! I just want to make sure there's no better solution, then I will mark yours as accepted. – Yad Smood Jul 29 '13 at 15:20
  • I cleaned it up a bit, made it actually do async type stuff, but no there isn't any way to clean it up significantly. Even third party control flow libraries end up looking pretty bad syntax wise. – MobA11y Jul 29 '13 at 15:25