Josh Gordon Josh Gordon - 5 months ago 9
Node.js Question

Derived node.js EventEmitter will call the wrong listener when in a named object property

I've been working on creating my own home automation hub using a handful of node.js packages (Express, mqtt, socket.io) with a MongoDB database and Angular running in the client. This project is the first time I've worked with any JavaScript, so it's safe to say I'm a bit of a noob.

I've been troubleshooting an issue with derived EventEmitters calling the wrong listener function when referenced as a named property. Here's a minimal example that will demonstrate the problem:

var EventEmitter = require('events').EventEmitter;
var debug = require('debug')('test.js');
var util = require('util');

var TestEmitter = function (initialState, name) {
var self = this;
EventEmitter.call(self);
this.state = initialState;
this.name = name;
this.setState = function (newState) {
self.emit('change', newState, this.state);
self.state = newState;
};
};
util.inherits(TestEmitter, EventEmitter);

var myObj = {
ary: [new TestEmitter(false, 'ary1'), new TestEmitter(true, 'ary2')],
named: {
name1: new TestEmitter(3, 'name1'),
name2: new TestEmitter(4, 'name2')
}
};

myObj.ary.forEach(function (aryEmitter) {
aryEmitter.on('change', function (newState) {
debug(aryEmitter.name + ' changed to: ' + newState);
});
});
for (var prop in myObj.named) {
var currEmitter = myObj.named[prop];
debug('prop = ' + prop);
debug('name = ' + currEmitter.name);
currEmitter.on('change', function (newState) {
debug(currEmitter.name + ' changed to: ' + newState);
});
}

myObj.ary[0].setState(true);
myObj.ary[1].setState(false);
myObj.named.name1.setState(4);
myObj.named.name2.setState(5);


Essentially,
TestEmitter
is an object derived from
EventEmitter
that will broadcast a
change
event when its
setState
method is called.

myObj
is a reference to four
TestEmitter
instances -- two in an array, and two in named properties. After creating
myObj
, I register a listener for each of their
change
events that simply writes debugging output to the console with the name of the
TestEmitter
instance whose callback is being invoked.

However, the named
TestEmitter
references don't work the way that I would expect. The call to both
myObj.named.name1.setState(4)
as well as
myObj.named.name2.setState(5)
will both execute the callback function that I registered for the
EventEmitter
myObj.named.name2
. Running everything above will produce the following output:

test.js prop = name1 +0ms
test.js name = name1 +5ms
test.js prop = name2 +0ms
test.js name = name2 +0ms
test.js ary1 changed to: true +0ms
test.js ary2 changed to: false +0ms
test.js name2 changed to: 4 +0ms
test.js name2 changed to: 5 +0ms


Can anyone offer any help? I've read a fair amount about the best way to create derived
EventEmitters
and it looks like I'm taking the right approach, so I'm a bit stumped.

Thanks for reading and any help you're able to offer!

Answer

Your problem becomes at this part of the code:

for (var prop in myObj.named) {
    var currEmitter = myObj.named[prop];
    debug('prop = ' + prop);
    debug('name = ' + currEmitter.name);
    currEmitter.on('change', function (newState) {
        debug(currEmitter.name + ' changed to: ' + newState);
    });
}

So that when an event is fired, inside the function handler, currEmitter has the value of the last element of loop.

One simple fix is to use ES6 Arrow functions:

for (var prop in myObj.named) {
    var currEmitter = myObj.named[prop];
    currEmitter.on('change', newState=>{
        debug(currEmitter.name + ' changed to: ' + newState);
    });
}

Or you can use Object.keys():

Object.keys(myObj.named).forEach(function(key){
  var currEmitter = myObj.named[key];
  currEmitter.on('change', function(newState){
    debug(currEmitter.name + ' changed to: ' + newState);
  });
});
Comments