From 2a68899e2550dd07381d97e124ee17dde840e6bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Sat, 27 Feb 2021 16:38:16 +0100 Subject: [PATCH] add support for EventTarget in once Port of https://github.com/nodejs/node/pull/29498 --- events.js | 11 ++++++ package.json | 1 + tests/events-once.js | 92 +++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 103 insertions(+), 1 deletion(-) diff --git a/events.js b/events.js index edd45ce..3f1de98 100644 --- a/events.js +++ b/events.js @@ -448,6 +448,17 @@ function unwrapListeners(arr) { function once(emitter, name) { return new Promise(function (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, function eventListener () { + // Remove it manually: IE8 does not support `{ once: true }` + emitter.removeEventListener(name, eventListener); + resolve([].slice.call(arguments)); + }); + return; + } + function eventListener() { if (errorListener !== undefined) { emitter.removeListener('error', errorListener); diff --git a/package.json b/package.json index d936726..9810e97 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "devDependencies": { "airtap": "^1.0.0", "functions-have-names": "^1.2.1", + "has": "^1.0.3", "has-symbols": "^1.0.1", "isarray": "^2.0.5", "tape": "^5.0.0" diff --git a/tests/events-once.js b/tests/events-once.js index 309bf45..fe99785 100644 --- a/tests/events-once.js +++ b/tests/events-once.js @@ -3,8 +3,60 @@ var common = require('./common'); var EventEmitter = require('../').EventEmitter; var once = require('../').once; +var has = require('has'); var assert = require('assert'); +function EventTargetMock() { + this.events = {}; + + this.addEventListener = common.mustCall(this.addEventListener); + this.removeEventListener = common.mustCall(this.removeEventListener); +} + +EventTargetMock.prototype.addEventListener = function (name, listener, options) { + if (!(name in this.events)) { + this.events[name] = { listeners: [], options: options || {} } + } + this.events[name].listeners.push(listener); +}; + +EventTargetMock.prototype.removeEventListener = function (name, callback) { + if (!(name in this.events)) { + return; + } + var event = this.events[name]; + var stack = event.listeners; + + for (var i = 0, l = stack.length; i < l; i++) { + if (stack[i] === callback) { + stack.splice(i, 1); + if (stack.length === 0) { + delete this.events[name]; + } + return; + } + } +}; + +EventTargetMock.prototype.dispatchEvent = function (name) { + if (!(name in this.events)) { + return true; + } + + var arg = [].slice.call(arguments, 1); + + var event = this.events[name]; + var stack = event.listeners.slice(); + + for (var i = 0, l = stack.length; i < l; i++) { + stack[i].apply(null, arg); + if (event.options.once) { + this.removeEventListener(name, stack[i]); + } + } + return !name.defaultPrevented; +}; + function onceAnEvent() { var ee = new EventEmitter(); @@ -88,12 +140,50 @@ function onceError() { }); } +function onceWithEventTarget() { + var et = new EventTargetMock(); + process.nextTick(() => { + et.dispatchEvent('myevent', 42); + }); + return once(et, 'myevent').then(function (args) { + var value = args[0]; + assert.strictEqual(value, 42); + assert.strictEqual(has(et.events, 'myevent'), false); + }); +} + +function onceWithEventTargetTwoArgs() { + var et = new EventTargetMock(); + process.nextTick(() => { + et.dispatchEvent('myevent', 42, 24); + }); + return once(et, 'myevent').then(function (value) { + assert.deepStrictEqual(value, [42, 24]); + }); +} + +function onceWithEventTargetError() { + var et = new EventTargetMock(); + var expected = new Error('kaboom'); + process.nextTick(() => { + et.dispatchEvent('error', expected); + }); + return once(et, 'error').then(function (args) { + var error = args[0]; + assert.deepStrictEqual(error, expected); + assert.strictEqual(has(et.events, 'error'), false); + }); +} + Promise.all([ onceAnEvent(), onceAnEventWithTwoArgs(), catchesErrors(), stopListeningAfterCatchingError(), - onceError() + onceError(), + onceWithEventTarget(), + onceWithEventTargetTwoArgs(), + onceWithEventTargetError() ]).catch(function (err) { console.error(err.stack) process.exit(1)