0

I have code like this:

for (var index = 1; index < args.length; index++) {
    var shell = args[index];
    listener.of('/' + shell.namespace || 'shotgun')
        .on('connection', function (socket) {
            socket.on('execute', function (cmdStr, context, options) {
                console.log('Received: ' + cmdStr);
                var result = shell.execute(cmdStr, context, options);
                socket.emit('result', result);
            });
        });
}

I'm using socket.io and I'm creating multiple namespaces. The socket.io namespaces are working great, but in the execute callback the reference to shell is always the last object created in the for loop. I need each iteration of the loop to keep reference to the shell from that iteration.

CatDadCode
  • 58,507
  • 61
  • 212
  • 318

3 Answers3

3

Well, the eventual answer will be to use let (ECMAScript 6 or node --harmony):

for (let index = 1; index < args.length; index++) {
    let shell = args[index];
    // ...
}

In the meantime, you can use a closure, or an additional function scope, to keep a different index and shell for each iteration:

function iterator(shell, index) {
    listener.of('/' + shell.namespace || 'shotgun')
        // ...
}

for (var index = 1; index < args.length; index++) {
    iterator(args[index], index);
}

And, if args is an Array, this can also be done with .forEach():

args.forEach(function (shell, index) {
    listener.of('/' + shell.namespace || 'shotgun')
        // ...
});
Community
  • 1
  • 1
Jonathan Lonowski
  • 121,453
  • 34
  • 200
  • 199
2

Here is one of the solutions for your challenge using .bind.

for (var index = 1; index < args.length; index++) {
  var shell = args[index];
  listener.of('/' + shell.namespace || 'shotgun')
    .on('connection', function(socket) {
      shell = this.shell;
      socket.on('execute', function (cmdStr, context, options) {
        console.log('Received: ' + cmdStr);
        var result = shell.execute(cmdStr, context, options);
        socket.emit('result', result);
      });
    }.bind({ shell: shell }));
}
moka
  • 22,846
  • 4
  • 51
  • 67
2

The problem is that you are creating a closure that references the variable shell and evaluates the variable when the function is called, not when it is defined. You need to create a closure that references the current value of the loop variable. There are several ways, but one is to use an IIFE:

for (var index = 1; index < args.length; index++) {
    var shell = args[index];
    listener.of('/' + shell.namespace || 'shotgun')
        .on('connection', (function (_shell) { return function (socket) {
            socket.on('execute', function (cmdStr, context, options) {
                console.log('Received: ' + cmdStr);
                var result = _shell.execute(cmdStr, context, options);
                socket.emit('result', result);
            }; }(shell)));
        });
}

Note: You could use the same name (shell) for the formal argument to the IIFE, which would hide the variable in the outer scope, but I renamed it _shell to clarify what's going on.

Ted Hopp
  • 232,168
  • 48
  • 399
  • 521