summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJenia <jeniabrook@gmail.com>2019-08-19 01:13:26 +0300
committerRich Trott <rtrott@gmail.com>2019-09-22 17:48:21 -0700
commit34a61d563088c97f13e193d00c91cd893f1479b8 (patch)
tree36e093e6ad0947e053e86d4cc7ee3b32b42a2ad2
parent54c4139efd6a2063c5c20d5483935cd2ad4946fd (diff)
downloadandroid-node-v8-34a61d563088c97f13e193d00c91cd893f1479b8.tar.gz
android-node-v8-34a61d563088c97f13e193d00c91cd893f1479b8.tar.bz2
android-node-v8-34a61d563088c97f13e193d00c91cd893f1479b8.zip
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>
-rw-r--r--doc/api/events.md8
-rw-r--r--lib/events.js11
-rw-r--r--test/parallel/test-events-once.js87
3 files changed, 103 insertions, 3 deletions
diff --git a/doc/api/events.md b/doc/api/events.md
index 3b92a04ca5..8d40958232 100644
--- a/doc/api/events.md
+++ b/doc/api/events.md
@@ -703,11 +703,15 @@ added: v11.13.0
* `name` {string}
* 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'`.
The `Promise` will resolve with an array of all the arguments emitted to the
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
const { once, EventEmitter } = require('events');
@@ -735,7 +739,7 @@ async function run() {
run();
```
-
+[WHATWG-EventTarget](https://dom.spec.whatwg.org/#interface-eventtarget)
[`--trace-warnings`]: cli.html#cli_trace_warnings
[`EventEmitter.defaultMaxListeners`]: #events_eventemitter_defaultmaxlisteners
[`domain`]: domain.html
diff --git a/lib/events.js b/lib/events.js
index ebde85c6dd..f6e4996d20 100644
--- a/lib/events.js
+++ b/lib/events.js
@@ -497,6 +497,17 @@ function unwrapListeners(arr) {
function once(emitter, name) {
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) => {
if (errorListener !== undefined) {
emitter.removeListener('error', errorListener);
diff --git a/test/parallel/test-events-once.js b/test/parallel/test-events-once.js
index 25ef4e9845..fea143f587 100644
--- a/test/parallel/test-events-once.js
+++ b/test/parallel/test-events-once.js
@@ -4,6 +4,53 @@ const common = require('../common');
const { once, EventEmitter } = require('events');
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() {
const ee = new EventEmitter();
@@ -84,10 +131,48 @@ async function onceError() {
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([
onceAnEvent(),
onceAnEventWithTwoArgs(),
catchesErrors(),
stopListeningAfterCatchingError(),
- onceError()
+ onceError(),
+ onceWithEventTarget(),
+ onceWithEventTargetTwoArgs(),
+ onceWithEventTargetError(),
]).then(common.mustCall());