Skip to content

Commit

Permalink
fix EventTarget support
Browse files Browse the repository at this point in the history
  • Loading branch information
goto-bus-stop committed Feb 27, 2021
1 parent cfa2d6f commit f1d67b0
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 56 deletions.
64 changes: 36 additions & 28 deletions events.js
Original file line number Diff line number Diff line change
Expand Up @@ -448,40 +448,48 @@ 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 errorListener(err) {
emitter.removeListener(name, resolver);
reject(err);
}

function eventListener() {
if (errorListener !== undefined) {
function resolver() {
if (typeof emitter.removeListener === 'function') {
emitter.removeListener('error', errorListener);
}
resolve([].slice.call(arguments));
};
var errorListener;

// Adding an error listener is not optional because
// if an error is thrown on an event emitter we cannot
// guarantee that the actual event we are waiting will
// be fired. The result could be a silent way to create
// memory or file descriptor leaks, which is something
// we should avoid.
if (name !== 'error') {
errorListener = function errorListener(err) {
emitter.removeListener(name, eventListener);
reject(err);
};

emitter.once('error', errorListener);
}

emitter.once(name, eventListener);
eventTargetAgnosticAddListener(emitter, name, resolver, { once: true });
addErrorHandlerIfEventEmitter(emitter, errorListener, { once: true });
});
}

function addErrorHandlerIfEventEmitter(emitter, handler, flags) {
if (typeof emitter.on === 'function') {
eventTargetAgnosticAddListener(emitter, 'error', handler, flags);
}
}

function eventTargetAgnosticAddListener(emitter, name, listener, flags) {
if (typeof emitter.addEventListener === 'function') {
// EventTarget does not have `error` event semantics like Node
// EventEmitters, we do not listen for `error` events here.
emitter.addEventListener(name, function wrapListener(arg) {
// IE does not have builtin `{ once: true }` support so we
// have to do it manually.
if (flags.once) {
emitter.removeEventListener(name, wrapListener);
}
listener(arg);
});
} else if (typeof emitter.on === 'function') {
if (flags.once) {
emitter.once(name, listener);
} else {
emitter.on(name, listener);
}
} else {
throw new TypeError('The "emitter" argument must be of type EventEmitter. Received type ' + typeof emitter);
}
}
47 changes: 19 additions & 28 deletions tests/events-once.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,25 @@ var once = require('../').once;
var has = require('has');
var assert = require('assert');

function Event(type) {
this.type = type;
}

function EventTargetMock() {
this.events = {};

this.addEventListener = common.mustCall(this.addEventListener);
this.removeEventListener = common.mustCall(this.removeEventListener);
}

EventTargetMock.prototype.addEventListener = function (name, listener, options) {
EventTargetMock.prototype.addEventListener = function addEventListener(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) {
EventTargetMock.prototype.removeEventListener = function removeEventListener(name, callback) {
if (!(name in this.events)) {
return;
}
Expand All @@ -38,23 +42,21 @@ EventTargetMock.prototype.removeEventListener = function (name, callback) {
}
};

EventTargetMock.prototype.dispatchEvent = function (name) {
if (!(name in this.events)) {
EventTargetMock.prototype.dispatchEvent = function dispatchEvent(arg) {
if (!(arg.type in this.events)) {
return true;
}

var arg = [].slice.call(arguments, 1);

var event = this.events[name];
var event = this.events[arg.type];
var stack = event.listeners.slice();

for (var i = 0, l = stack.length; i < l; i++) {
stack[i].apply(null, arg);
stack[i].call(null, arg);
if (event.options.once) {
this.removeEventListener(name, stack[i]);
this.removeEventListener(arg.type, stack[i]);
}
}
return !name.defaultPrevented;
return !arg.defaultPrevented;
};

function onceAnEvent() {
Expand Down Expand Up @@ -142,36 +144,26 @@ function onceError() {

function onceWithEventTarget() {
var et = new EventTargetMock();
var event = new Event('myevent');
process.nextTick(function () {
et.dispatchEvent('myevent', 42);
et.dispatchEvent(event);
});
return once(et, 'myevent').then(function (args) {
var value = args[0];
assert.strictEqual(value, 42);
assert.strictEqual(value, event);
assert.strictEqual(has(et.events, 'myevent'), false);
});
}

function onceWithEventTargetTwoArgs() {
var et = new EventTargetMock();
process.nextTick(function () {
et.dispatchEvent('myevent', 42, 24);
});
return once(et, 'myevent').then(function (value) {
assert.strictEqual(value[0], 42);
assert.strictEqual(value[1], 24);
});
}

function onceWithEventTargetError() {
var et = new EventTargetMock();
var expected = new Error('kaboom');
var error = new Event('error');
process.nextTick(function () {
et.dispatchEvent('error', expected);
et.dispatchEvent(error);
});
return once(et, 'error').then(function (args) {
var error = args[0];
assert.strictEqual(error, expected);
var err = args[0];
assert.strictEqual(err, error);
assert.strictEqual(has(et.events, 'error'), false);
});
}
Expand All @@ -183,6 +175,5 @@ module.exports = Promise.all([
stopListeningAfterCatchingError(),
onceError(),
onceWithEventTarget(),
onceWithEventTargetTwoArgs(),
onceWithEventTargetError()
]);

0 comments on commit f1d67b0

Please sign in to comment.