Skip to content

Commit 2a68899

Browse files
committed
add support for EventTarget in once
Port of nodejs/node#29498
1 parent 1e934b7 commit 2a68899

File tree

3 files changed

+103
-1
lines changed

3 files changed

+103
-1
lines changed

events.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,17 @@ function unwrapListeners(arr) {
448448

449449
function once(emitter, name) {
450450
return new Promise(function (resolve, reject) {
451+
if (typeof emitter.addEventListener === 'function') {
452+
// EventTarget does not have `error` event semantics like Node
453+
// EventEmitters, we do not listen to `error` events here.
454+
emitter.addEventListener(name, function eventListener () {
455+
// Remove it manually: IE8 does not support `{ once: true }`
456+
emitter.removeEventListener(name, eventListener);
457+
resolve([].slice.call(arguments));
458+
});
459+
return;
460+
}
461+
451462
function eventListener() {
452463
if (errorListener !== undefined) {
453464
emitter.removeListener('error', errorListener);

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"devDependencies": {
2525
"airtap": "^1.0.0",
2626
"functions-have-names": "^1.2.1",
27+
"has": "^1.0.3",
2728
"has-symbols": "^1.0.1",
2829
"isarray": "^2.0.5",
2930
"tape": "^5.0.0"

tests/events-once.js

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,60 @@
33
var common = require('./common');
44
var EventEmitter = require('../').EventEmitter;
55
var once = require('../').once;
6+
var has = require('has');
67
var assert = require('assert');
78

9+
function EventTargetMock() {
10+
this.events = {};
11+
12+
this.addEventListener = common.mustCall(this.addEventListener);
13+
this.removeEventListener = common.mustCall(this.removeEventListener);
14+
}
15+
16+
EventTargetMock.prototype.addEventListener = function (name, listener, options) {
17+
if (!(name in this.events)) {
18+
this.events[name] = { listeners: [], options: options || {} }
19+
}
20+
this.events[name].listeners.push(listener);
21+
};
22+
23+
EventTargetMock.prototype.removeEventListener = function (name, callback) {
24+
if (!(name in this.events)) {
25+
return;
26+
}
27+
var event = this.events[name];
28+
var stack = event.listeners;
29+
30+
for (var i = 0, l = stack.length; i < l; i++) {
31+
if (stack[i] === callback) {
32+
stack.splice(i, 1);
33+
if (stack.length === 0) {
34+
delete this.events[name];
35+
}
36+
return;
37+
}
38+
}
39+
};
40+
41+
EventTargetMock.prototype.dispatchEvent = function (name) {
42+
if (!(name in this.events)) {
43+
return true;
44+
}
45+
46+
var arg = [].slice.call(arguments, 1);
47+
48+
var event = this.events[name];
49+
var stack = event.listeners.slice();
50+
51+
for (var i = 0, l = stack.length; i < l; i++) {
52+
stack[i].apply(null, arg);
53+
if (event.options.once) {
54+
this.removeEventListener(name, stack[i]);
55+
}
56+
}
57+
return !name.defaultPrevented;
58+
};
59+
860
function onceAnEvent() {
961
var ee = new EventEmitter();
1062

@@ -88,12 +140,50 @@ function onceError() {
88140
});
89141
}
90142

143+
function onceWithEventTarget() {
144+
var et = new EventTargetMock();
145+
process.nextTick(() => {
146+
et.dispatchEvent('myevent', 42);
147+
});
148+
return once(et, 'myevent').then(function (args) {
149+
var value = args[0];
150+
assert.strictEqual(value, 42);
151+
assert.strictEqual(has(et.events, 'myevent'), false);
152+
});
153+
}
154+
155+
function onceWithEventTargetTwoArgs() {
156+
var et = new EventTargetMock();
157+
process.nextTick(() => {
158+
et.dispatchEvent('myevent', 42, 24);
159+
});
160+
return once(et, 'myevent').then(function (value) {
161+
assert.deepStrictEqual(value, [42, 24]);
162+
});
163+
}
164+
165+
function onceWithEventTargetError() {
166+
var et = new EventTargetMock();
167+
var expected = new Error('kaboom');
168+
process.nextTick(() => {
169+
et.dispatchEvent('error', expected);
170+
});
171+
return once(et, 'error').then(function (args) {
172+
var error = args[0];
173+
assert.deepStrictEqual(error, expected);
174+
assert.strictEqual(has(et.events, 'error'), false);
175+
});
176+
}
177+
91178
Promise.all([
92179
onceAnEvent(),
93180
onceAnEventWithTwoArgs(),
94181
catchesErrors(),
95182
stopListeningAfterCatchingError(),
96-
onceError()
183+
onceError(),
184+
onceWithEventTarget(),
185+
onceWithEventTargetTwoArgs(),
186+
onceWithEventTargetError()
97187
]).catch(function (err) {
98188
console.error(err.stack)
99189
process.exit(1)

0 commit comments

Comments
 (0)