Search code examples
javascriptnode.jsclosureseventemitter

Closure issue with creating listeners for emitted events


I've implemented a object that encapsulates a driver which interacts with external devices. When the driver receives data it emits an event.

When I start, I load a list of all defined drivers. Then I iterate through the definitions to do the following:

  • create a driver instance,
  • listen for the events on the driver, and
  • tell the driver to connect.

As a part of the listener for the emitted event I define a function that is passed some details on what the device represents.

However my code appears to have a problem. When a driver emits an event, the function that is fired only shows one set of details. These details are the last ones defined in the for loop where I create the drivers and listen for the events.

I've created a test example that recreates my problem by using a couple of simple objects and a timer. This will need to be run in node.js to see the problem.

var EventEmitter = require('events').EventEmitter;
var util         = require('util');

// define vehicle object
function Vehicle(options) {
    options = options || {};
    this.id = ((options.id != null) ? options.id : -1);
    this.name = ((options.name != null) ? options.name : 'unknown');
}

util.inherits(Driver, EventEmitter);

// define driver object
function Driver(options) {
    options = options || {};
    this.id = ((options.id != null) ? options.id : -1);
    this.name = ((options.name != null) ? options.name : 'unknown');
}

Driver.prototype.startTimer = function(delay) {
    self = this;

    console.log('starting timer for ' + self.name + ' with a delay of ' + delay);

    setTimeout(function() {
        console.log('firing timer for ' + self.name);
        self.emit('timer', 'some data');
    }, delay);
}

// function to create listener
function makeOnTimerFunction(driver, vehicle) {
    console.log("creating function for d name : " + driver.name + ' d id: ' + driver.id + ' v id: ' + vehicle.id + ' v name: ' + vehicle.name);
    return function(d) {
        console.log(" d name: " + driver.name + ' d id: ' + driver.id + ' v id: ' + vehicle.id + ' v name: ' + vehicle.name + ' d: ' + d);
    }
}

var vehicles = new Array();
vehicles.push(new Vehicle({id: 1001, name: 'Max'}));
vehicles.push(new Vehicle({id: 1002, name: 'Cheif'}));
vehicles.push(new Vehicle({id: 1003, name: 'Seigfreid'}));
var driver = null;

for (var i = 0; i < 3; i++) {

    driver = new Driver({
        id: vehicles[i].id,
        name: vehicles[i].name,
    });

    driver.on('timer', makeOnTimerFunction(driver, vehicles[i]));
    driver.startTimer(1000 * i);
}

```

The output I get is as follows:

creating function for d name : Max d id: 1001 v id: 1001 v name: Max
starting timer for Max with a delay of 0
creating function for d name : Cheif d id: 1002 v id: 1002 v name: Cheif
starting timer for Cheif with a delay of 1000
creating function for d name : Seigfreid d id: 1003 v id: 1003 v name: Seigfreid
starting timer for Seigfreid with a delay of 2000
firing timer for Seigfreid
 d name: Seigfreid d id: 1003 v id: 1003 v name: Seigfreid d: some data
firing timer for Seigfreid
 d name: Seigfreid d id: 1003 v id: 1003 v name: Seigfreid d: some data
firing timer for Seigfreid
 d name: Seigfreid d id: 1003 v id: 1003 v name: Seigfreid d: some data

What I expect to see is as follows:

creating function for d name : Max d id: 1001 v id: 1001 v name: Max
starting timer for Max with a delay of 0
creating function for d name : Cheif d id: 1002 v id: 1002 v name: Cheif
starting timer for Cheif with a delay of 1000
creating function for d name : Seigfreid d id: 1003 v id: 1003 v name: Seigfreid
starting timer for Seigfreid with a delay of 2000
firing timer for Max
 d name: Max d id: 1001 v id: 1001 v name: Max d: some data
firing timer for Chief
 d name: Chief d id: 1002 v id: 1002 v name: Chief d: some data
firing timer for Seigfried
 d name: Seigfreid d id: 1003 v id: 1003 v name: Seigfreid d: some data

The issue is that the timer is always referencing the last object, named Seigfreid.

---- edit ----

Just to make the problem clear the problem appears to be with the makeOnTimerFunction function. The function it returns is the function to fire when an event occurs. When makeOnTimerFunction is called, the variables all make sense. However when the event fires and the function returned by makeOnTimerFunction is called then the variables are always the same. They do not reflect the values when makeOnTimerFunction was first called.


Solution

  • Here's your problem:

    Driver.prototype.startTimer = function(delay) {
      self = this; <----
    

    You're effectively creating a global variable self, which gets overwritten every time you run the startTimer method. In the end, it will be overwritten so it points to the last driver instance, hence your unexpected results.

    Solution: use var to create a new variable scoped to the method:

    Driver.prototype.startTimer = function(delay) {
      var self = this;