Skip to content

Commit

Permalink
add support for EventTarget in once
Browse files Browse the repository at this point in the history
  • Loading branch information
goto-bus-stop committed Feb 27, 2021
1 parent 1e934b7 commit 2a68899
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 1 deletion.
11 changes: 11 additions & 0 deletions events.js
Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -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"
Expand Down
92 changes: 91 additions & 1 deletion tests/events-once.js
Expand Up @@ -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();

Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit 2a68899

Please sign in to comment.