events: add support for EventTarget in once
PR-URL: https://github.com/nodejs/node/pull/29498 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Minwoo Jung <minwoo@nodesource.com>
This commit is contained in:
parent
54c4139efd
commit
34a61d5630
@ -703,11 +703,15 @@ added: v11.13.0
|
|||||||
* `name` {string}
|
* `name` {string}
|
||||||
* Returns: {Promise}
|
* Returns: {Promise}
|
||||||
|
|
||||||
Creates a `Promise` that is resolved when the `EventEmitter` emits the given
|
Creates a `Promise` that is fulfilled when the `EventEmitter` emits the given
|
||||||
event or that is rejected when the `EventEmitter` emits `'error'`.
|
event or that is rejected when the `EventEmitter` emits `'error'`.
|
||||||
The `Promise` will resolve with an array of all the arguments emitted to the
|
The `Promise` will resolve with an array of all the arguments emitted to the
|
||||||
given event.
|
given event.
|
||||||
|
|
||||||
|
This method is intentionally generic and works with the web platform
|
||||||
|
[EventTarget](WHATWG-EventTarget) interface, which has no special
|
||||||
|
`'error'` event semantics and does not listen to the `'error'` event.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const { once, EventEmitter } = require('events');
|
const { once, EventEmitter } = require('events');
|
||||||
|
|
||||||
@ -735,7 +739,7 @@ async function run() {
|
|||||||
|
|
||||||
run();
|
run();
|
||||||
```
|
```
|
||||||
|
[WHATWG-EventTarget](https://dom.spec.whatwg.org/#interface-eventtarget)
|
||||||
[`--trace-warnings`]: cli.html#cli_trace_warnings
|
[`--trace-warnings`]: cli.html#cli_trace_warnings
|
||||||
[`EventEmitter.defaultMaxListeners`]: #events_eventemitter_defaultmaxlisteners
|
[`EventEmitter.defaultMaxListeners`]: #events_eventemitter_defaultmaxlisteners
|
||||||
[`domain`]: domain.html
|
[`domain`]: domain.html
|
||||||
|
@ -497,6 +497,17 @@ function unwrapListeners(arr) {
|
|||||||
|
|
||||||
function once(emitter, name) {
|
function once(emitter, name) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
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,
|
||||||
|
(...args) => { resolve(args); },
|
||||||
|
{ once: true }
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const eventListener = (...args) => {
|
const eventListener = (...args) => {
|
||||||
if (errorListener !== undefined) {
|
if (errorListener !== undefined) {
|
||||||
emitter.removeListener('error', errorListener);
|
emitter.removeListener('error', errorListener);
|
||||||
|
@ -4,6 +4,53 @@ const common = require('../common');
|
|||||||
const { once, EventEmitter } = require('events');
|
const { once, EventEmitter } = require('events');
|
||||||
const { strictEqual, deepStrictEqual } = require('assert');
|
const { strictEqual, deepStrictEqual } = require('assert');
|
||||||
|
|
||||||
|
class EventTargetMock {
|
||||||
|
constructor() {
|
||||||
|
this.events = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
addEventListener = common.mustCall(function(name, listener, options) {
|
||||||
|
if (!(name in this.events)) {
|
||||||
|
this.events[name] = { listeners: [], options };
|
||||||
|
}
|
||||||
|
this.events[name].listeners.push(listener);
|
||||||
|
});
|
||||||
|
|
||||||
|
removeEventListener = common.mustCall(function(name, callback) {
|
||||||
|
if (!(name in this.events)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const event = this.events[name];
|
||||||
|
const stack = event.listeners;
|
||||||
|
|
||||||
|
for (let i = 0, l = stack.length; i < l; i++) {
|
||||||
|
if (stack[i] === callback) {
|
||||||
|
stack.splice(i, 1);
|
||||||
|
if (stack.length === 0) {
|
||||||
|
Reflect.deleteProperty(this.events, name);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatchEvent = function(name, ...arg) {
|
||||||
|
if (!(name in this.events)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const event = this.events[name];
|
||||||
|
const stack = event.listeners.slice();
|
||||||
|
|
||||||
|
for (let i = 0, l = stack.length; i < l; i++) {
|
||||||
|
stack[i].apply(this, arg);
|
||||||
|
if (event.options.once) {
|
||||||
|
this.removeEventListener(name, stack[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return !name.defaultPrevented;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async function onceAnEvent() {
|
async function onceAnEvent() {
|
||||||
const ee = new EventEmitter();
|
const ee = new EventEmitter();
|
||||||
|
|
||||||
@ -84,10 +131,48 @@ async function onceError() {
|
|||||||
strictEqual(ee.listenerCount('myevent'), 0);
|
strictEqual(ee.listenerCount('myevent'), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function onceWithEventTarget() {
|
||||||
|
const et = new EventTargetMock();
|
||||||
|
|
||||||
|
process.nextTick(() => {
|
||||||
|
et.dispatchEvent('myevent', 42);
|
||||||
|
});
|
||||||
|
const [ value ] = await once(et, 'myevent');
|
||||||
|
strictEqual(value, 42);
|
||||||
|
strictEqual(Reflect.has(et.events, 'myevent'), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onceWithEventTargetTwoArgs() {
|
||||||
|
const et = new EventTargetMock();
|
||||||
|
|
||||||
|
process.nextTick(() => {
|
||||||
|
et.dispatchEvent('myevent', 42, 24);
|
||||||
|
});
|
||||||
|
|
||||||
|
const value = await once(et, 'myevent');
|
||||||
|
deepStrictEqual(value, [42, 24]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onceWithEventTargetError() {
|
||||||
|
const et = new EventTargetMock();
|
||||||
|
|
||||||
|
const expected = new Error('kaboom');
|
||||||
|
process.nextTick(() => {
|
||||||
|
et.dispatchEvent('error', expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
const [err] = await once(et, 'error');
|
||||||
|
strictEqual(err, expected);
|
||||||
|
strictEqual(Reflect.has(et.events, 'error'), false);
|
||||||
|
}
|
||||||
|
|
||||||
Promise.all([
|
Promise.all([
|
||||||
onceAnEvent(),
|
onceAnEvent(),
|
||||||
onceAnEventWithTwoArgs(),
|
onceAnEventWithTwoArgs(),
|
||||||
catchesErrors(),
|
catchesErrors(),
|
||||||
stopListeningAfterCatchingError(),
|
stopListeningAfterCatchingError(),
|
||||||
onceError()
|
onceError(),
|
||||||
|
onceWithEventTarget(),
|
||||||
|
onceWithEventTargetTwoArgs(),
|
||||||
|
onceWithEventTargetError(),
|
||||||
]).then(common.mustCall());
|
]).then(common.mustCall());
|
||||||
|
Loading…
x
Reference in New Issue
Block a user