-
Notifications
You must be signed in to change notification settings - Fork 29.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add context argument to emitter #15763
Comments
Why not use |
I don't want to bind the handler. I want to leave the handler's binding as-is (the emitter). I just want to pass some random context the same way I can pass it to most other functional interfaces and timers. But more importantly, why force everyone to bind handlers when we can instead just pass an argument? This is the most common use case of emitters... |
I'm generally +1 on the need for this, as a technical detail adding a new argument does have a non-zero risk of breaking existing code. There is another approach I've been considering that also covers another discussion that's been ongoing... that is, adding a new API to emitter.addHandler((event, context, ...args) => { /* ... */ }, context); Here, the handler is invoked for every emitted event, with the event name passed as the first argument. I've augmented what I was thinking previously with the additional Because this is a new API, it wouldn't have a risk of breaking existing stuff. |
@jasnell I like it a lot, even better than my idea! |
On second thought, I need to be able to choose the events because I’m dealing with streams and I don’t want to get called on every chuck of data. |
Ok. I think the same principle applies with regards to adding a new api. But definitely can work with that constraint |
And your argument order is the correct one where |
Ok, so there are a couple of requirements from this and the other discussion ... that I think we can easily capture in a single new API. (we can bikeshed the name of the new function later) emitter.addListenerWithContext( [ 'event1', 'event2' ], context, (event, context, ...args) => { /* ... */ }); The @mcollina @silverwind ... what do you think? |
Wouldn't that make |
Node core itself could strongly benefit from having something like proposed above ( |
Can you add benchmarks between a) using bind vs b) changing the request? I think it would be interesting data for everyone. I'm 👍 with this change. I would prefer the 3rd parameter to As you said, the current best practice involves modifying the request, however this often leads to every request having different "shapes" on the V8 eyes, and it prevents a great deal of optimisations from their side. If you are mostly interested in just attaching a context to req and res, you might want to have a look at #15752 and #15560. I think they might solve your issue in a different way (@hekike is working on Restify at the moment), and they have almost zero side-effects and potential for regressions. |
Instead of adding a third boolean parameter for |
Aside from performance, is there an advantage to adding a It seems to me that this isn't a very common use case (in most code paths, the slight performance penalty of using a closure is worthwhile for the benefit of more idiomatic code). I agree with @medikoo's comment in #15763 (comment) that this seems a bit like feature creep. If we do add this to the
|
Yeah I agree that if we should add this at all in core it should be a different method. I still don't understand why function handler(context, arg) {
// context === req
// arg === 'bar'
}
// ...
req.on('foo', handler.bind(req, contextArg));
req.emit('foo', 'bar'); |
https://jsperf.com/emitter-bind-vs-context 25% better is meaningful. |
@hueniverse FWIW I only get a ~8-9% difference with current node master when comparing actual built-in It seems V8 is getting better with calling bound functions. |
My best argument is that every new JS API is providing this functionality one way or another. If we are not making any changes/new APIs, fine. But if we are still evolving the node APIs, this seems like an easy win. I also think node has been historically more aggressive at looking for 8-9% gains... |
Could you provide some examples? I personally haven't noticed a pattern like this. |
@not-an-aardvark |
I would just like to note that the |
Somewhat related: #13338 - which was voted down for being unidiomatic. My .02: if the performance gap is currently < 10%, a better strategy is to try and close that gap than add an ad hoc new API.
But they inherit that from If a |
With bind performance these days I don't really see the issue with just binding? I'm sure there are plenty other APIs that require bind for similar things. |
Relevant: v8/v8@594803c |
I like the Do you have a nice solution for removing an binded callback without saving it? |
@Ulrikop No, that's just something you have to learn to live with. You'd have the same issue with a context object. You should be able to add the same function with different contexts but then you need the context object again for disambiguation when you remove one. |
Closing due to lack of further progress. |
As part of the hapi v17 work, I removed every closure from the hot path. This provided significant performance improvements. All these closures came from handlers passed to node EventEmitters (specifically from
req
andres
object). In order to do that, I had to hang my own property on the emitter object. For example:This works and can be improved by using symbols, but it's still messy. It also means adding multiple context properties per handler because they might need different context data. Instead, I would like to be able to do something as simple as:
The idea is to pass a third optional argument to
on()
and then append that argument to the end of the emit arguments list. We already store listeners as objects with properties. When this feature is not used, it means two additionalif
statements: once to check if a third argument was provided and another to check if one is present and needs to be appended. If no third argument is provided, these two extraif
statements should not have any noticeable impact on performance.However, since 99% of the time, emitter handlers require the creation of a callback closure, this should make applications using it much faster overall. Potentially, this can be used internally by node as well.
There is already a pattern for this in
setTimeout()
. Most new JS features with callbacks now accept some kind of extra binding argument. Because we already bind the handler to the emitter, we cannot use this pattern.I'm happy to do the work if the idea is acceptable.
The text was updated successfully, but these errors were encountered: