When using nodejs event system, I met with an annoying problem. As the code below shows, when a listener catch a event, the event emitter object owns 'this' in the callback function instead of the listener.
It's not a big problem if you put the callback in the listener's constructor, because besides pointer 'this', you can still use other variables defined in the constructor's scope, like the 'self' or 'that' way.
But, if the callback is put outside the constructor like the prototype methods, it appears to me that there is no ways to get the listener's 'this'.
Not very sure if there are any other solutions. And also, a little confused about why nodejs event emit use the emitter as the caller install of the listener?
util = require('util');
EventEmitter = require('events').EventEmitter;
var listener = function () {
var pub = new publisher();
var self = this;
pub.on('ok', function () {
console.log('by listener constructor this: ', this instanceof listener);
// output: by listener constructor this: false
console.log('by listener constructor self: ', self instanceof listener);
// output: by listener constructor this: true
})
pub.on('ok', this.outside);
}
listener.prototype.outside = function () {
console.log('by prototype listener this: ', this instanceof listener);
// output: by prototype listener this: false
// how to access to listener's this here?
}
var publisher = function () {
var self = this;
process.nextTick(function () {
self.emit('ok');
})
}
util.inherits(publisher, EventEmitter);
var l = new listener();
Try binding the listener explicitly to the callback:
pub.on('ok', this.outside.bind(this));
Related
why this code compiles
var Person = function() {
console.log("CALLED PERSON")};
Person.prototype.saySomething = function() {
console.log("saySomething PERSON")};
var ape = new Person();
ape.saySomething();
and this code throws error Cannot set property 'saySomething' of undefined
var Person = async function() {
console.log("CALLED PERSON")};
Person.prototype.saySomething = function() {
console.log("saySomething PERSON")};
var ape = new Person();
ape.saySomething();
When you use async function() {}, you are declaring an asynchronous function object. That's different than a regular function object. The asynchronous function object does not have a prototype.
So, when you try to do this:
var Person = async function() {
console.log("CALLED PERSON")
};
Person.prototype.saySomething = function() {
console.log("saySomething PERSON")
};
Person.prototype is undefined because there is no prototype on an asynchronous function object. Thus attempting to assign something to Person.prototype.saySomething causes the error you see because Person.prototype is undefined.
There is some logic to this because an asynchronous function can't be used as a constructor because an asynchronous function always returns a promise so it can't ever return a new object as in let obj = new f(). So, there's no purpose in having a .prototype property because it can't be used that way.
If you really wanted to asynchronously create an object, you could always create an async factory function that returns a promise that resolves with an object.
It is possible to add an async function in the end of the prototype chain.
Please notice that this works well in nodejs 8.11.1+.
// lets start with defining some cool async function who takes the time and
// awaits a promise
async function someCoolAsyncFunction() {
// this let will be returned after timeout in a different value.
let coolStuff = 'still boring';
// do something cool which takes time
await new Promise((resolve, reject) => setTimeout(() => {
coolStuff = 'an epiphany';
resolve();
}, 1000))
return coolStuff;
}
// Now let's define the 'regular' prototype chain with it's boring functions.
function Person(p) {
this.constructorPropery = p;
return this;
}
Person.prototype.notAsync = function() {
// do something regular in the prototype chain
console.log("Let's build some ",this.constructorPropery)
this.kindOfPerson = 'Regular and boring';
return this;
}
// And now, lets add an async function to this chain
Person.prototype.someAsyncFunction = async function() {
// you will still have access to 'this' in this function as long as the
// previous function in the prototype chain returnes 'this'
console.log('I used to be someone ',this.kindOfPerson);
// Now, this is our time to shine, lets await something cool
this.lifeChangingEvent = await someCoolAsyncFunction();
console.log('Until I had ',this.lifeChangingEvent);
a.kindOfPerson = 'enlightened';
console.log('and now I am ', a.kindOfPerson);
return this;
}
So this will work:
new Person('charachter').notAsync().someAsyncFunction();
But this WILL NOT work:
new Person('charachter').someAsyncFunction().notAsync();
And if you really need the data in 'this' outside the prototype chain you can also do:
let myself = new Person('charachter').notAsync();
console.log('myself.kindOfPerson is: ',myself.kindOfPerson);
myself.someAsyncFunction();
console.log('myself.kindOfPerson now is: ',myself.kindOfPerson);
Be sure to remember which prototype is an async function for which function or use your own naming convection for that.
I don't understand how the function(msg)... is called with myEmmiter.emit.
How does the .emit('someEvent', ...) know that the argument 'the event was emitted' should be passed to function(msg)...?
Is there a way to see the emit method source?
const events = require('events');
var myEmmiter = new events.EventEmitter();
myEmmiter.on('someEvent', function (msg) {
console.log(msg);
});
myEmmiter.emit('someEvent', 'the event was emitted');
The implementation for the events module can be found here: https://github.com/nodejs/node/blob/master/lib/events.js
In abstract, an EventEmitter instance maintains an object where the keys are the event names (someEvent) and the values are arrays of functions that should be called whenever that event is generated:
this.registeredEvents = {
someEvent : [ handler ]
};
To register for an event, you use emitter.on()/emitter.addListener():
on(eventName, handler) {
if (! Array.isArray(this.registeredEvents[eventName])) {
this.registeredEvents[eventName] = [];
}
this.registeredEvents[eventName].push(handler);
}
When you call emitter.emit(), each handler is called with the argument:
emit(eventName, message) {
(this.registeredEvents[eventName] || []).forEach(handler => handler(message));
}
I am learning node.js. On the nodejs api website there is a piece of code that I don't really get.
The link is here
var util = require("util");
var events = require("events");
function MyStream() {
events.EventEmitter.call(this);
}
util.inherits(MyStream, events.EventEmitter);
MyStream.prototype.write = function(data) {
this.emit("data", data);
}
var stream = new MyStream();
console.log(stream instanceof events.EventEmitter); // true
console.log(MyStream.super_ === events.EventEmitter); // true
stream.on("data", function(data) {
console.log('Received data: "' + data + '"');
})
stream.write("It works!"); // Received data: "It works!"
so the confusing part is
events.EventEmitter.call(this);
What does it do here?
MyStream is a new object declaration that inherits behaviors from events.EventEmitter as can be seen from this line where the inheritance is configured:
util.inherits(MyStream, events.EventEmitter);
So, when the MyStream constructor is invoked usually via something like var stream = new MyStream();, it needs to also invoke the constructor of the object that it inherits from so the parent object can initialize itself properly. That's what this line is:
events.EventEmitter.call(this);
events.EventEmitter is the constructor of the object that MyStream inherits from. events.EventEmitter.call(this) instructs Javascript to call that constructor with the this pointer set to this object.
If you need more help with understanding .call(), you can read this MDN reference.
Currently I have a Node.js module with the following form :
var events = require('events');
var emitter = new events.EventEmitter();
function emitSomething() {
emitter.emit("event");
}
exports.emitSomething = emitSomething;
exports.on = emitter.on;
However any callback registered through on do not get called when I call emitSomething.
I can get around this by changing the last line to
exports.on = function(event, callback) { emitter.on(event, callback); };
Is there a reason I can't delegate the on function to a function defined in another module?
This is a common JS mistake. Note that the on method depends on this (which is emitter in your example). However, all you export is the function itself, so it's context (emitter) is lost when you call it later.
Here is a small example for this issue:
var someObj = {
doSth: function() { console.log(this) }
};
someObj.doSth(); // "Object { ..."
var doSth = someObj.doSth;
doSth(); // "Window { ..."
When I call doSth as method of someObj, this references someObj as expected. After I copied the function to doSth, this references it's new context, in the browser the global context Window. This is what happens in your case.
As you already mentioned, you have to bind the emitter context to the function. This can also be done like this:
exports.on = emitter.on.bind(emitter);
I'm trying to add the event listener to my class, but it fails, telling me the object has no 'on' method.
Here's the class in its own file:
var events = require('events');
var util = require('util');
var Motion = function Motion (app) {
events.EventEmitter.call(this);
// Load models
app.loadModel('motion', 'motion');
this.on('testevent', function () {
console.log('an event has happened');
});
this.emit('testevent');
}
util.inherits(Motion, events.EventEmitter);
module.exports = Motion;
And here's how I instantiate it:
var Motion = require('./plugins/motion.js');
var motion = new Motion(app);
It looks like you may be asking for the constructor function itself to be an event emitter. Your code makes the objects produced by new with the constructor. i.e., the object motion produced at the end of your snippet should have an on method (as Vadim Baryshev notes, your code should work as you have it if this is the intent, and if that is the case you can ignore the rest of this answer).
If you really want the constructor to emit events, then take a look at this question and the answer I provided to it. However, it's not a great solution, and there appears to be no way of doing it without using the non-standard __proto__.
A better solution is to make a separate event emitter object for the constructor function to use for emissions. As much as I like the constructor-module pattern, it has to be discarded. Many node modules make the module itself the emitter, and have a function exposed by the module as a constructor. For example:
var events = require('events');
var util = require('util');
exports = module.exports = new events.EventEmitter();
exports.Motion = function (app) {
// Load models
app.loadModel('motion', 'motion');
// Emit event
exports.emit('testevent');
};
and to instantiate:
var motion = require('./plugins/motion');
motion.on('testevent', function () {
console.log('an object was constructed');
});
var motionObj = new motion.Motion(app);