2

I was coding some Node.js and I thought, coming from a Java background, that I should figure out how to create my own "wait" functionality. Obviously, it will be a little different since Java is designed to be multi-threaded and Node is not. Anyway, the desired functionality I want in Node is that I want to create a blocking function myself - such that if some boolean in the program is not set, I might want to call process.nextTick() until the boolean gets set.

So it would be:

    var bool = false;

    function foo(){
    var b = baz();
    }

    function baz(){

        while(!bool){
           process.nextTick(function() {
           return baz(arguments);
        });
      }

    return {};
    }

   function setBool(number) {

       setTimeout(function () {
            bool = false;
       }, number);
   }

   process.nextTick(wait);
   setBool(3000);

so this question is twofold:

(1) is this the best way to implement a waiting type functionality in Node.js?

(2) If I called baz(arguments) instead the baz function (recursively) what happens in terms of the execution? Generally, when you want the return value from function X, but function X calls itself a few times before returning, does this pose any problems? Is it no big deal and it turns out to be the same return value? I have thought about this before but never really worried about it or investigated it.

Alexander Mills
  • 90,741
  • 139
  • 482
  • 817
  • 3
    This is brilliant, but a horrible antipattern. You should never wait in node. – lxe May 18 '15 at 20:27
  • 1
    There's a couple of times it's probably OK to 'wait' in Node, when a server is first starting up or when you are running a script that depends on a condition that might be set by an outside process, but o/w yeah. – Alexander Mills May 18 '15 at 20:45

3 Answers3

6

Do not 'wait' in Node.js. "Waiting" blocks ALL other execution. That's an important note because if you block, then the boolean can NEVER set changed. JavaScript, and especially in Node, uses an event driven model. Do this, when that.

So the best way? It's opinion based but this is one way:

var bool = false;

function foo() {
    console.log('Listening for true');
    baz(function() {
        //Do something when called
        console.log('It\'s finally true!');
    });
}

function baz(callback) {
    setImmediate(function() {
        if (bool)
            callback();
        else
            baz(callback);
    });
}

foo();
setTimeout(function() {
    console.log('Setting to true');
    bool = true;
}, 4000);
Randy
  • 4,351
  • 2
  • 25
  • 46
  • 1
    `process` is a global Object in Node. It doesn't exist in your browser. – Randy May 18 '15 at 21:47
  • :) yes of course, I forgot, someone please create a node.js fiddle :) and someone fix jsFiddle so it throws an error. – Alexander Mills May 18 '15 at 21:50
  • 1
    I updated my answer to use `setImmediate` as `process.nextTick` is going to be deprecated. I tested it and it does work. – Randy May 18 '15 at 21:51
  • huh let me try it again – Alexander Mills May 18 '15 at 21:51
  • 1
    You cheated! :) process.nextTick doesn't work, but you're right setImmediate( ) does. If you get it to succeed with process.nextTick I'd be curious to see what that looks like. @Chris Tavares, Randy did get it to work with setImmediate( ). – Alexander Mills May 18 '15 at 21:56
  • Shhh. You're right. `process.nextTick` didn't work as I was thinking it would. But setImmediate does. – Randy May 18 '15 at 22:00
  • well, the solution with setImmediate is pretty badass, and now we all get to learn some important differences between process.nextTick, despite it being deprecated, and setImmediate. – Alexander Mills May 18 '15 at 22:02
  • It works because it doesn't loop directly, it returns to the event loop and checks in the "next tick". This is a very different solution, and you may notice that it's not synchronous - you still need to put the following code into a callback, not just as the next statement after the function returns. – Chris Tavares May 18 '15 at 22:03
  • the only question left is if this solution indeed blocks, or if it allows other functions in the queue to run; I believe the latter is true, which makes it a good solution. – Alexander Mills May 18 '15 at 22:05
  • var bool = false; function foo() { console.log('Listening for true'); baz(function() { //Do something when called console.log('It\'s finally true!'); process.exit(0); }); } function baz(callback) { setImmediate(function() { if (bool){ //console.log('callback') callback(); } else{ //console.log('baz(callback)') baz(callback); } }); } setInterval(function(){ console.log('doing other work part 1'); },1); setTimeout(function() { console.log('Setting to true'); bool = true; }, 1500); foo(); setInterval(function(){ console.log('doing other work part 2'); },1); – Alexander Mills May 18 '15 at 22:12
  • http://stackoverflow.com/questions/17502948/nexttick-vs-setimmediate-visual-explanation this Q/A has the answer to the main difference between process.nextTick and setImmediate – Alexander Mills May 18 '15 at 22:56
  • I guess I was originally wondering how to do this without a callback - the callback passed to baz( ). It seems that fibers and generators are the only way to write synchronous style code in node. And I agree it's not a good idea. Using callbacks is much more efficient. – Alexander Mills May 22 '15 at 19:16
6

The other answers have already stated the important part: No, this will not work. I wanted to post this to provide a little more background on why this will not work.

Under the hood, node.js is running an event dispatching loop. There is a queue of events (including, at least conceptually, "app start", "incoming message", "socket connection", "file io complete", etc.). As events happen, the event dispatcher pulls the event off the queue and calls any Javascript functions associated with that event. Once the callback functions are completed, the dispatch then pulls the next event off the queue, processes it, etc. This is completely synchronous - the next event cannot be processed until the current one has completed.

So when you have code like this:

var done = false;
while (!done) {
    process.nextTick(function () {
       done = true;
    });
}

what happens? You enter the while loop, the process.nextTick puts an event on the event queue to be processed the next time the event loop runs. Then your code loops back through the while, checks done (still false), queues another event, etc.

Since the event loop can't process the queued event until your function returns, the event loop gets stuck and the callback for process.nextTick never gets called. You've ensured there never is a "nextTick".

You cannot do a synchronous wait in pure Javascript code. In Node.js you can do it in C extensions (that's how stuff like fs.readFileSync are implemented). There are some Javascript compiler tricks you can use (like streamline or co) to write code that looks synchronous. But under the hood they're all using the async callback pattern.

My recommendation is to learn the core callback pattern first so you understand it. It is, granted, a giant pain. From there investigate promises (I personally like Bluebird as a promise implementation, or if you're using ES6 you can use the built-in promises) or one of the many async helper libraries.

Chris Tavares
  • 29,165
  • 4
  • 46
  • 63
  • Randy's answer looked interesting; I tried to get it to work and it seems to suffer from the same ailment that you described in your answer. Here is a jsFiddle basically representing Randy's solution: https://jsfiddle.net/oresoftware/2hkzm99f/ I debugged it and it never gets out of the wait() function. the setTimeout argument (anon function) never gets invoked, so the bool never gets set to true. – Alexander Mills May 18 '15 at 21:50
  • I have a question.. is the nextTick queue in this example going to be full after some time ? what happens then ? – Youssef Mohamed Mar 02 '21 at 06:30
1

A typical way to do it in Javascript is the use of promises. For instance, with "q" promises (see https://github.com/kriskowal/q ) you would have :

var deferred = Q.defer();


deferred.promise.then(function(myParam) {
    var b = baz();
});
function baz(){
    return {/*whatever you want*/};
}

And, somewhere else in the code:

deferred.resolve(myParam);

In this example, you make sure that "baz" is called only after some condition is met. Basically, instead of setting your boolean to true, you "resolve" the deferred object.

Joel
  • 2,374
  • 17
  • 26