Search code examples
javascriptalgorithmnode.jsv8

In nodejs, is there a better design pattern to call async functions synchronously?


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.


Solution

  • 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.