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!
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 encapsule it in a function, to create a new scope, and do the things inside this function :
for (var prop in myObj.named) {
var currEmitter = myObj.named[prop];
(function(currEmitter){
currEmitter.on('change', newState=>{
debug(currEmitter.name + ' changed to: ' + newState);
});
})(currEmitter);
}
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);
});
});