-
Notifications
You must be signed in to change notification settings - Fork 30k
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
events: support EventTarget in once
#27977
events: support EventTarget in once
#27977
Conversation
Add support for EventTarget in the `once` static method.
Co-Authored-By: Anna Henningsen <github@addaleax.net>
Would you mind adding some docs? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this currently doesn't quite do the right thing? Left some notes.
@@ -509,6 +509,17 @@ function once(emitter, name) { | |||
emitter.once('error', errorListener); | |||
} | |||
|
|||
emitter.once(name, eventListener); | |||
if (typeof emitter.once === 'function' && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I could be going totally crazy but I feel like this function is incorrect and the test only passes because it runs through the EventEmitter path? I think you need something more like this?
function once(emitter, name) {
return new Promise((resolve, reject) => {
if (typeof emitter.once === 'function' &&
typeof emitter.removeListener === 'function') {
const eventListener = (...args) => {
if (errorListener !== undefined) {
emitter.removeListener('error', errorListener);
}
resolve(args);
};
let errorListener;
// Adding an error listener is not optional because
// if an error is thrown on an event emitter we cannot
// guarantee that the actual event we are waiting will
// be fired. The result could be a silent way to create
// memory or file descriptor leaks, which is something
// we should avoid.
if (name !== 'error') {
errorListener = (err) => {
emitter.removeListener(name, eventListener);
reject(err);
};
emitter.once('error', errorListener);
}
emitter.once(name, eventListener);
return;
}
if (typeof emitter.addEventListener === 'function') {
// EventTarget does not have `error` event semantics like Node
// EventEmitters, we do not listen to `error` events here.
emitter.addEventListener(name, resolve, { once: true });
return;
}
throw new ERR_INVALID_ARG_TYPE('emitter', 'EventEmitter', emitter);
});
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure what the difference here - but this can be a brainfart on my behalf
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Your original PR still binds the error
listener (and unnecessarily creates the eventListener
function). It also creates a nested Promise that it then resolves so the original never resolves. I could also be missing something... but when I ran the code, it didn't seem to do what you're expecting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@apapirovski ah wow you're right!
this.on(name, listener); | ||
} | ||
}); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add emitter.removeListener = undefined;
to hit the right code path in this test?
given event. | ||
|
||
Note: This method is intentionally generic and works with web platform | ||
[EventTarget](WHATWG-EventTarget) instances. `EventTarget`s have no special | ||
`'error'` event semantics and do not listen to the `'error'` event. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Worth mentioning the different argument semantics? It resolves with the Event
instance rather than an array of arguments.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@apapirovski I was not sure about what to do here - what behavior would you think is less surprising?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More saying we should make clear the distinction in arguments that the callback receives when you use this with EventTarget
.
Co-Authored-By: Joyee Cheung <joyeec9h3@gmail.com>
Co-Authored-By: Joyee Cheung <joyeec9h3@gmail.com>
…gr/io.js into event-emitter-once-event-target
return emitter.once(name, eventListener); | ||
} | ||
if (typeof emitter.addEventListener === 'function') { | ||
return new Promise((resolve) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extra Promise not needed here?
process.nextTick(() => { | ||
emitter.emit('myevent', 42); | ||
}); | ||
const [value] = await once(emitter, 'myevent'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also check that there are no interesting error semantics?
Superceded by #29498 (review) :] |
Add support for EventTarget in the
once
static method, hopefully this makes it a bit more universal.Ping @mcollina please take a look.
(I can also revert the if the error change is to big)