1

I am new to node.js and very inexpert when it comes to async processing.

I have a list of functions in an array, which I need to call asynchronously when a message arrives. I planned to use process.nextTick to schedule the calls. This is my (simplified) code:

// These are example message handlers
// All have to be called on every message
function fn1( msg ) { console.log( "fn1: " + msg ) };
function fn2( msg ) { console.log( "fn2: " + msg ) };
function fn3( msg ) { console.log( "fn3: " + msg ) };

// the functions to be called are saved in a list
var fns = [ fn1, fn2, fn3 ];

// function to schedule async callbacks to every
// function in the list.
function multicall( msg ){
    // schedule the call back to each function
    for( var ix = 0; ix < fns.length; ix++ ){
        var fn = fns[ix];
        process.nextTick( function() {
                // call the nth function with the message
                fn( msg );
            }
        );
    }
}

// now make the call to schedule callbacks
console.log( "start" );
multicall( "information" );
console.log( "end" );

The calls are queued as expected, but the problem is that when the calls are made, they are all to the last function, instead of one call to each function. Presumably this is because the call to fn( msg ) actually references the value of the variable fn at the time of the call, not at the time that the function is was scheduled. The calls are therefore all made to the last function in the list:

S:\test>node asyncTest
start
end
fn3: information
fn3: information
fn3: information

I have tried various approaches but can't seem to get round this problem of the difference between the function referenced when the call is scheduled, and the function referenced when the call is executed.

EDIT - - - - Although I accept that @jishi's answer below is better, I inadvertently found another answer to this problem, which is to use let fn = fns[ix]; instead of var fn = fns[ix];. When using let, the for loop creates a completely new variable on each iteration, so the value set in one cycle is not overwritten by the value set in the next cycle. I hadn't previously understood that the scope of a let variable is limited to each specific execution of the code block, not just by the static lexical structure of the code.

JohnRC
  • 1,251
  • 1
  • 11
  • 12
  • Just saw your edit. The `var` vs `let` is because of variable hoisting, if you want to read up on it. I didn't think of the fact that for loops is it's own scope when using let, that's a good point. However, you will run into similar cases where it won't be that simple to fix in your continued use of Node.js :) – jishi Mar 14 '19 at 13:57
  • Thanks @jishi - I thought variable hoisting was more about where in the lexical scope a var can be referenced, rather than its actual creation - I will read up more. The point about using `let` in a loop block is that it could be really useful when referencing objects that are used by async operations launched within the loop. – JohnRC Mar 20 '19 at 12:03

1 Answers1

3

That's because the fn variable will be in scope for all of the scheduled calls, and you actually change fn.

You should just send in the reference of the function to the nextTick (and maybe rather use setImmediate()) and bind the variable that you want the function to be executed with, something like this:

process.nextTick(fn.bind(null, msg));

This will schedule the function currently assigned to fn, with null as this reference, and the current value of msg as first argument to the function.

With setImmediate, I think you can do this:

setImmediate(fn, msg)

Because first argument is function, and the following arguments are arguments for the function that will be used when invoking (similar to bind())

In my example, I actually schedule the function as a reference, whereas in your example, you are scheduling an anonymous function that lives in the same scope as multicall(), meaning that any changes in the multicall scope, will affect execution of your anonymous function.

Before bind() existed, you had to sort of declare a function, send in the variables you wanted to be set when execution the function, and then return a new function from that. Very cumbersome, and I argue that should be avoided since the bind() approaches makes it in a much neater way.

jishi
  • 24,126
  • 6
  • 49
  • 75
  • Wow thanks @jishi! - I just changed the code to use fn.bind as you suggested and it works a treat. – JohnRC Mar 01 '19 at 15:54
  • Glad it helped @JohnRC. May I suggest reading this as well to get an understanding of the difference between nextTick and setImmediate: https://stackoverflow.com/questions/15349733/setimmediate-vs-nexttick – jishi Mar 01 '19 at 15:57
  • I also changed to use `setImmediate( fn.bind( null, msg ) );` instead of `process.nextTick(..)` as you suggested - that also seems to work fine, and should be more friendly to the node.js processing cycle. – JohnRC Mar 01 '19 at 16:07
  • setImmediate doesn't require bind, because it already supports argument binding (see https://nodejs.org/api/timers.html#timers_setimmediate_callback_args), but either way is fine. – jishi Mar 01 '19 at 21:31