Passing arguments to setInterval is causing strange behaviour

I'm making a simple script reads out the nginx server status in node.js I don't think my problem has to do with node.js itself, but more an issue with how I'm using the setInterval() function.

I don't want to paste all the code, because that makes a bit confusing to read. When you run this code, you see this:

pre-Timer for web25
pre-Timer for web26
pre-Timer for web27
pre-Timer for web28
pre-Timer for web29
Timer for web29
Fetch host? web29
Timer for web29
Fetch host? web29
Timer for web29
Fetch host? web29
Timer for web29
Fetch host? web29
Timer for web29
Fetch host? web29

As you can see, the timer is only using the last host of the loop. Somehow it's not making a copy of the variable to the setInterval scope.

What am I doing wrong?

Part of the code:

var http = require('http');

StatsNginxMapper.prototype = {

nginxServers: new Object(),
mysql: null,
mysqlClient: null,
statsDb: 'serverstats',
userMapper: null,

init: function() {
    this.mysql = require('mysql');
    this.mysqlClient = /* mysql stuff */;

    this.collectData();
},

setUserMapper: function(mapper) {
    this.userMapper = mapper;
},

collectData: function() {
    this.collectServers();
},

collectServers: function() {

    var self = this;
    var server = null;

    /* Normally this is done through MySQL, but for now lets do it manually */

    server = new StatsNginxServer();
    server.setHost('web25');
    this.nginxServers['web25'] = server;

    server = new StatsNginxServer();
    server.setHost('web26');
    this.nginxServers['web26'] = server;

    server = new StatsNginxServer();
    server.setHost('web27');
    this.nginxServers['web27'] = server;

    server = new StatsNginxServer();
    server.setHost('web28');
    this.nginxServers['web28'] = server;

    server = new StatsNginxServer();
    server.setHost('web29');
    this.nginxServers['web29'] = server;

    this.loopServers();

},

loopServers: function() {

    for(var host in this.nginxServers) {
        var nginxServer = this.nginxServers[host];

        if(nginxServer.hasTimer()) continue;

        var self = this;
        console.log('pre-Timer for ' + host);

        var timerId = setInterval(function() {

            console.log('Timer for ' + host);

            self.getData(host);

            }, 2000);

        nginxServer.setTimer(timerId);

    }

},

getData: function(host) {
    console.log('Fetch host? ' + host);
},

}

Change the loopServers function to this:

loopServers: function() {

    for(var host in this.nginxServers) {            
        var nginxServer = this.nginxServers[host];

        if(nginxServer.hasTimer()) continue;

        var self = this;
        console.log('pre-Timer for ' + host);
        (function(host, nginxServer, self) {
            var timerId = setInterval(function() {    
                console.log('Timer for ' + host);   
                self.getData(host);   
            }, 2000);

            nginxServer.setTimer(timerId);
        })(host, nginxServer, self);
    };
}

This will create a closure around the setInterval code that will preserve the values of the variables at that point.

Information about closures

The problem is that the function that runs on an interval, executes with the variables that are in scope when it calls. What that means, is that the host that is 'visible' to every intervaled call, is the last one in the loop (in your case, web29).

The solution is to make sure that the function that runs will always have the right one in scope. One way of doing this is:

var timerId = setInterval(function(host) {
    return function() {
        console.log('Timer for ' + host);
        self.getData(host);
    }
}(host), 2000);

Here, we create a new function, that returns the function you wish to run. And we run that function immediately upon creation, passing through the value of 'host' at that time. This way, the function that does the logging, will always have the host that was in scope at the time of creation.

I've tried to simplify the language to make it clearer, but I'm not sure I've succeeded. If you read through what I wrote, hopefully it should see clear.