emittery
Version:
Simple and modern async event emitter
1,054 lines (857 loc) • 26.2 kB
JavaScript
import {
anyMap,
producersMap,
eventsMap,
lifecycleMap,
} from './maps.js';
const anyProducer = Symbol('anyProducer');
const resolvedPromise = Promise.resolve();
// Define symbols for "meta" events.
const listenerAdded = Symbol('listenerAdded');
const listenerRemoved = Symbol('listenerRemoved');
const metaEventsAllowed = new WeakMap();
const metaEventsPermitted = new WeakMap();
const suppressAllEnqueue = Symbol('suppressAllEnqueue');
const suppressedEventsMap = new WeakMap();
let isGlobalDebugEnabled = false;
const isEventKeyType = key => typeof key === 'string' || typeof key === 'symbol' || typeof key === 'number';
function makeDisposable(function_) {
function_[Symbol.dispose] = function_;
return function_;
}
function addAbortListener(signal, listener, {swallowErrors = false} = {}) {
if (!signal) {
return () => {};
}
const onAbort = () => {
if (swallowErrors) {
try {
listener();
} catch {}
return;
}
listener();
};
if (signal.aborted) {
onAbort();
return () => {};
}
signal.addEventListener('abort', onAbort, {once: true});
return () => {
signal.removeEventListener('abort', onAbort);
};
}
function assertEventName(eventName) {
if (!isEventKeyType(eventName)) {
throw new TypeError('`eventName` must be a string, symbol, or number');
}
}
function assertListener(listener) {
if (typeof listener !== 'function') {
throw new TypeError('listener must be a function');
}
}
function getListeners(instance, eventName) {
const events = eventsMap.get(instance);
if (!events.has(eventName)) {
return;
}
return events.get(eventName);
}
function getEventProducers(instance, eventName) {
const key = isEventKeyType(eventName) ? eventName : anyProducer;
const producers = producersMap.get(instance);
if (!producers.has(key)) {
return;
}
return producers.get(key);
}
function enqueueProducers(instance, eventName, eventData, hasEventData) {
if (isEnqueueSuppressed(instance, eventName)) {
return;
}
const producers = producersMap.get(instance);
if (!producers.has(eventName) && !producers.get(anyProducer)?.size) {
return;
}
const resolvedEventData = Promise.resolve(eventData);
const makeEvent = async () => makeEventObject(eventName, await resolvedEventData, hasEventData);
if (producers.has(eventName)) {
for (const producer of producers.get(eventName)) {
producer.enqueue(makeEvent());
}
}
if (producers.has(anyProducer)) {
for (const producer of producers.get(anyProducer)) {
producer.enqueue(makeEvent());
}
}
}
function iterator(instance, eventNames, {signal} = {}) {
eventNames = Array.isArray(eventNames) ? eventNames : [eventNames];
let isFinished = false;
let flush = () => {};
let queue = [];
let removeAbortListener = () => {};
const producer = {
enqueue(item) {
queue.push(item);
flush();
},
finish() {
isFinished = true;
removeAbortListener();
flush();
},
};
for (const eventName of eventNames) {
const producerKey = isEventKeyType(eventName) ? eventName : anyProducer;
let set = getEventProducers(instance, eventName);
if (!set) {
set = new Set();
const producers = producersMap.get(instance);
producers.set(producerKey, set);
}
set.add(producer);
}
const removeProducer = () => {
for (const eventName of eventNames) {
const producerKey = isEventKeyType(eventName) ? eventName : anyProducer;
const set = getEventProducers(instance, eventName);
if (set) {
set.delete(producer);
if (set.size === 0) {
const producers = producersMap.get(instance);
producers.delete(producerKey);
}
}
}
};
const stop = () => {
if (!queue) {
return;
}
queue = undefined;
removeAbortListener();
removeProducer();
flush();
};
removeAbortListener = addAbortListener(signal, stop);
return {
async next() {
if (!queue) {
return {done: true};
}
if (queue.length === 0) {
if (isFinished) {
stop();
return this.next();
}
const {promise, resolve} = Promise.withResolvers();
flush = resolve;
await promise;
return this.next();
}
return {
done: false,
value: await queue.shift(),
};
},
async return(value) {
stop();
return arguments.length > 0
? {done: true, value: await value}
: {done: true};
},
[Symbol.asyncIterator]() {
return this;
},
async [Symbol.asyncDispose]() {
await this.return();
},
};
}
function defaultMethodNamesOrAssert(methodNames) {
if (methodNames === undefined) {
return allEmitteryMethods;
}
if (!Array.isArray(methodNames)) {
throw new TypeError('`methodNames` must be an array of strings');
}
for (const methodName of methodNames) {
if (!allEmitteryMethods.includes(methodName)) {
if (typeof methodName !== 'string') {
throw new TypeError('`methodNames` element must be a string');
}
throw new Error(`${methodName} is not Emittery method`);
}
}
return methodNames;
}
const isMetaEvent = eventName => eventName === listenerAdded || eventName === listenerRemoved;
function withSuppressedEnqueue(instance, eventNames, function_) {
const keys = eventNames.some(name => !isEventKeyType(name))
? [suppressAllEnqueue]
: eventNames;
let suppressed = suppressedEventsMap.get(instance);
if (!suppressed) {
suppressed = new Set();
suppressedEventsMap.set(instance, suppressed);
}
// Track only the keys we actually added, so re-entrant calls don't prematurely lift suppression.
const added = [];
for (const key of keys) {
if (!suppressed.has(key)) {
suppressed.add(key);
added.push(key);
}
}
try {
return function_();
} finally {
for (const key of added) {
suppressed.delete(key);
}
if (suppressed.size === 0) {
suppressedEventsMap.delete(instance);
}
}
}
function isEnqueueSuppressed(instance, eventName) {
const suppressed = suppressedEventsMap.get(instance);
if (!suppressed) {
return false;
}
return suppressed.has(suppressAllEnqueue) || suppressed.has(eventName);
}
function callInitFn(instance, lifecycle, listener, {eventName, set}) {
try {
const result = lifecycle.initFn();
if (typeof result === 'function') {
lifecycle.deinitFn = result;
}
} catch (error) {
set.delete(listener);
if (set.size === 0) {
eventsMap.get(instance).delete(eventName);
}
throw error;
}
}
function callAndUnsetDeinitFn(lifecycle) {
const deinitFn = lifecycle?.deinitFn;
if (deinitFn) {
lifecycle.deinitFn = undefined;
deinitFn();
}
}
const subscribeAction = 'subscribe';
const unsubscribeAction = 'unsubscribe';
function transitionEventListener(instance, {eventName, listener, action, swallowLifecycleError = false, removeResubscribedListener = false}) {
if (action === subscribeAction) {
let set = getListeners(instance, eventName);
if (!set) {
set = new Set();
eventsMap.get(instance).set(eventName, set);
}
const wasEmpty = set.size === 0;
const alreadyListening = set.has(listener);
set.add(listener);
if (!isMetaEvent(eventName) && wasEmpty && !isEnqueueSuppressed(instance, eventName)) {
const lifecycle = lifecycleMap.get(instance).get(eventName);
if (lifecycle) {
callInitFn(instance, lifecycle, listener, {eventName, set});
}
}
return {hasSet: true, changed: !alreadyListening};
}
const set = getListeners(instance, eventName);
if (!set) {
return {hasSet: false, changed: false};
}
const removed = set.delete(listener);
if (set.size === 0) {
eventsMap.get(instance).delete(eventName);
const lifecycle = lifecycleMap.get(instance).get(eventName);
if (swallowLifecycleError) {
try {
callAndUnsetDeinitFn(lifecycle);
} catch {}
} else {
callAndUnsetDeinitFn(lifecycle);
}
if (removeResubscribedListener) {
// Deinit can re-subscribe the same listener; keep rollback authoritative.
const setAfterDeinit = getListeners(instance, eventName);
setAfterDeinit?.delete(listener);
if (setAfterDeinit?.size === 0) {
eventsMap.get(instance).delete(eventName);
}
}
}
return {hasSet: true, changed: removed};
}
function emitSubscriptionSideEffects(instance, {eventName, listener, action, swallowErrors = false}) {
const isSubscribe = action === subscribeAction;
const debugType = isSubscribe ? 'subscribe' : 'unsubscribe';
const metaEvent = isSubscribe ? listenerAdded : listenerRemoved;
if (swallowErrors) {
try {
instance.logIfDebugEnabled(debugType, eventName, undefined);
} catch {}
if (!isMetaEvent(eventName)) {
try {
emitMetaEvent(instance, metaEvent, {eventName, listener});
} catch {}
}
return;
}
instance.logIfDebugEnabled(debugType, eventName, undefined);
if (!isMetaEvent(eventName)) {
emitMetaEvent(instance, metaEvent, {eventName, listener});
}
}
function rollbackAddedListeners(instance, eventNames, listener) {
withSuppressedEnqueue(instance, eventNames, () => {
for (const eventName of eventNames) {
const {hasSet} = transitionEventListener(instance, {
eventName,
listener,
action: unsubscribeAction,
swallowLifecycleError: true,
removeResubscribedListener: true,
});
if (!hasSet) {
continue;
}
emitSubscriptionSideEffects(instance, {
eventName,
listener,
action: unsubscribeAction,
swallowErrors: true,
});
}
});
}
function finishAndClearProducers(instance, eventName) {
const producers = getEventProducers(instance, eventName);
if (producers) {
for (const producer of producers) {
producer.finish();
}
producers.clear();
}
}
function finishAndClearAllProducers(instance) {
const allProducers = producersMap.get(instance);
for (const [key, producers] of allProducers.entries()) {
for (const producer of producers) {
producer.finish();
}
producers.clear();
allProducers.delete(key);
}
}
const makeEventObject = (eventName, eventData, hasEventData) =>
hasEventData ? {name: eventName, data: eventData} : {name: eventName};
function emitMetaEvent(emitter, eventName, eventData) {
metaEventsAllowed.set(emitter, (metaEventsAllowed.get(emitter) ?? 0) + 1);
metaEventsPermitted.set(emitter, (metaEventsPermitted.get(emitter) ?? 0) + 1);
try {
Emittery.prototype.emit.call(emitter, eventName, eventData);
} finally {
metaEventsAllowed.set(emitter, (metaEventsAllowed.get(emitter) ?? 0) - 1);
}
}
export default class Emittery {
static mixin(emitteryPropertyName, methodNames) {
methodNames = defaultMethodNamesOrAssert(methodNames);
return (target, _context) => {
if (typeof target !== 'function') {
throw new TypeError('`target` must be function');
}
for (const methodName of methodNames) {
if (target.prototype[methodName] !== undefined) {
throw new Error(`The property \`${methodName}\` already exists on \`target\``);
}
}
function getEmitteryProperty() {
Object.defineProperty(this, emitteryPropertyName, {
enumerable: false,
value: new Emittery(),
});
return this[emitteryPropertyName];
}
Object.defineProperty(target.prototype, emitteryPropertyName, {
enumerable: false,
get: getEmitteryProperty,
});
const emitteryMethodCaller = methodName => function (...args) {
return this[emitteryPropertyName][methodName](...args);
};
for (const methodName of methodNames) {
Object.defineProperty(target.prototype, methodName, {
enumerable: false,
value: emitteryMethodCaller(methodName),
});
}
return target;
};
}
static get isDebugEnabled() {
// In a browser environment, `globalThis.process` can potentially reference a DOM Element with a `#process` ID,
// so instead of just type checking `globalThis.process`, we need to make sure that `globalThis.process.env` exists.
// eslint-disable-next-line n/prefer-global/process
if (typeof globalThis.process?.env !== 'object') {
return isGlobalDebugEnabled;
}
// eslint-disable-next-line n/prefer-global/process
const {env} = globalThis.process ?? {env: {}};
return env.DEBUG === 'emittery' || env.DEBUG === '*' || isGlobalDebugEnabled;
}
static set isDebugEnabled(newValue) {
isGlobalDebugEnabled = newValue;
}
constructor(options = {}) {
anyMap.set(this, new Set());
eventsMap.set(this, new Map());
producersMap.set(this, new Map());
lifecycleMap.set(this, new Map());
producersMap.get(this).set(anyProducer, new Set());
for (const methodName of allEmitteryMethods) {
Object.defineProperty(this, methodName, {
value: this[methodName].bind(this),
writable: true,
enumerable: false,
configurable: true,
});
}
this.debug = options.debug ?? {};
if (this.debug.enabled === undefined) {
this.debug.enabled = false;
}
this.debug.logger ||= (type, debugName, eventName, eventData) => {
try {
// TODO: Use https://github.com/sindresorhus/safe-stringify when the package is more mature. Just copy-paste the code.
eventData = JSON.stringify(eventData);
} catch {
eventData = `Object with the following keys failed to stringify: ${Object.keys(eventData).join(',')}`;
}
if (typeof eventName === 'symbol' || typeof eventName === 'number') {
eventName = eventName.toString();
}
const currentTime = new Date();
const logTime = `${currentTime.getHours()}:${currentTime.getMinutes()}:${currentTime.getSeconds()}.${currentTime.getMilliseconds()}`;
console.log(`[${logTime}][emittery:${type}][${debugName}] Event Name: ${eventName}\n\tdata: ${eventData}`);
};
}
logIfDebugEnabled(type, eventName, eventData) {
if (Emittery.isDebugEnabled || this.debug.enabled) {
this.debug.logger(type, this.debug.name, eventName, eventData);
}
}
on(eventNames, listener, {signal} = {}) {
assertListener(listener);
eventNames = Array.isArray(eventNames) ? eventNames : [eventNames];
const addedEventNames = [];
try {
for (const eventName of eventNames) {
assertEventName(eventName);
const {changed} = transitionEventListener(this, {
eventName,
listener,
action: subscribeAction,
});
if (changed) {
addedEventNames.push(eventName);
}
emitSubscriptionSideEffects(this, {
eventName,
listener,
action: subscribeAction,
});
}
} catch (error) {
rollbackAddedListeners(this, addedEventNames, listener);
throw error;
}
let removeAbortListener = () => {};
const noError = Symbol('no-error');
const off = () => {
removeAbortListener();
let firstError = noError;
for (const eventName of eventNames) {
try {
this.off(eventName, listener);
} catch (error) {
firstError = firstError === noError ? error : firstError;
}
}
if (firstError !== noError) {
throw firstError;
}
};
removeAbortListener = addAbortListener(signal, off, {swallowErrors: true});
return makeDisposable(off);
}
off(eventNames, listener) {
assertListener(listener);
eventNames = Array.isArray(eventNames) ? eventNames : [eventNames];
for (const eventName of eventNames) {
assertEventName(eventName);
transitionEventListener(this, {
eventName,
listener,
action: unsubscribeAction,
});
emitSubscriptionSideEffects(this, {
eventName,
listener,
action: unsubscribeAction,
});
}
}
once(eventNames, predicateOrOptions) {
const {promise, resolve, reject} = Promise.withResolvers();
let off = () => {};
let signal;
let isSettled = false;
let removeAbortListener = () => {};
eventNames = Array.isArray(eventNames) ? [...eventNames] : [eventNames];
try {
let predicate;
if (typeof predicateOrOptions === 'function') {
predicate = predicateOrOptions;
} else if (typeof predicateOrOptions === 'object' && predicateOrOptions !== null) {
predicate = predicateOrOptions.predicate;
signal = predicateOrOptions.signal;
} else if (predicateOrOptions !== undefined) {
throw new TypeError('predicate must be a function');
}
if (predicate !== undefined && typeof predicate !== 'function') {
throw new TypeError('predicate must be a function');
}
if (signal?.aborted) {
throw signal.reason;
}
let listener = () => {};
const unsubscribe = () => {
removeAbortListener();
const noError = Symbol('no-error');
let firstError = noError;
for (const eventName of eventNames) {
try {
this.off(eventName, listener);
} catch (error) {
firstError = firstError === noError ? error : firstError;
}
}
if (firstError !== noError) {
throw firstError;
}
};
const unsubscribeAndSettle = () => {
unsubscribe();
isSettled = true;
};
listener = event => {
if (predicate && !predicate(event)) {
return;
}
if (isSettled) {
return;
}
try {
unsubscribeAndSettle();
} catch (error) {
reject(error);
return;
}
resolve(event);
};
this.on(eventNames, listener);
off = unsubscribe;
removeAbortListener = addAbortListener(signal, () => {
if (isSettled) {
return;
}
try {
unsubscribeAndSettle();
} catch {}
isSettled = true;
reject(signal.reason);
});
promise.off = () => {
if (isSettled) {
return;
}
unsubscribeAndSettle();
};
} catch (error) {
reject(error);
}
if (promise.off === undefined) {
promise.off = off;
}
return promise;
}
events(eventNames, {signal} = {}) {
eventNames = Array.isArray(eventNames) ? eventNames : [eventNames];
for (const eventName of eventNames) {
assertEventName(eventName);
}
return iterator(this, eventNames, {signal});
}
async emit(eventName, eventData) {
assertEventName(eventName);
if (isMetaEvent(eventName)) {
const remainingPermits = metaEventsPermitted.get(this) ?? 0;
if ((metaEventsAllowed.get(this) ?? 0) === 0 || remainingPermits === 0) {
throw new TypeError('`eventName` cannot be meta event `listenerAdded` or `listenerRemoved`');
}
metaEventsPermitted.set(this, remainingPermits - 1);
}
if (!isMetaEvent(eventName)) {
this.logIfDebugEnabled('emit', eventName, eventData);
}
const hasEventData = arguments.length > 1;
enqueueProducers(this, eventName, eventData, hasEventData);
const listeners = getListeners(this, eventName) ?? new Set();
const anyListeners = anyMap.get(this);
const staticListeners = [...listeners];
const staticAnyListeners = isMetaEvent(eventName) ? [] : [...anyListeners];
await resolvedPromise;
const results = await Promise.allSettled([
...staticListeners.map(async listener => {
if (listeners.has(listener)) {
return listener(makeEventObject(eventName, eventData, hasEventData));
}
}),
...staticAnyListeners.map(async listener => {
if (anyListeners.has(listener)) {
return listener(makeEventObject(eventName, eventData, hasEventData));
}
}),
]);
const errors = results.values()
.filter(result => result.status === 'rejected')
.map(result => result.reason)
.toArray();
if (errors.length > 0) {
throw new AggregateError(errors, 'One or more listeners threw an error');
}
}
async emitSerial(eventName, eventData) {
assertEventName(eventName);
if (isMetaEvent(eventName)) {
const remainingPermits = metaEventsPermitted.get(this) ?? 0;
if ((metaEventsAllowed.get(this) ?? 0) === 0 || remainingPermits === 0) {
throw new TypeError('`eventName` cannot be meta event `listenerAdded` or `listenerRemoved`');
}
metaEventsPermitted.set(this, remainingPermits - 1);
}
if (!isMetaEvent(eventName)) {
this.logIfDebugEnabled('emitSerial', eventName, eventData);
}
const hasEventData = arguments.length > 1;
enqueueProducers(this, eventName, eventData, hasEventData);
const listeners = getListeners(this, eventName) ?? new Set();
const anyListeners = anyMap.get(this);
const staticListeners = [...listeners];
const staticAnyListeners = isMetaEvent(eventName) ? [] : [...anyListeners];
await resolvedPromise;
/* eslint-disable no-await-in-loop */
for (const listener of staticListeners) {
if (listeners.has(listener)) {
await listener(makeEventObject(eventName, eventData, hasEventData));
}
}
for (const listener of staticAnyListeners) {
if (anyListeners.has(listener)) {
await listener(makeEventObject(eventName, eventData, hasEventData));
}
}
/* eslint-enable no-await-in-loop */
}
onAny(listener, {signal} = {}) {
assertListener(listener);
this.logIfDebugEnabled('subscribeAny', undefined, undefined);
anyMap.get(this).add(listener);
emitMetaEvent(this, listenerAdded, {listener});
let removeAbortListener = () => {};
const offAny = () => {
removeAbortListener();
this.offAny(listener);
};
removeAbortListener = addAbortListener(signal, offAny, {swallowErrors: true});
return makeDisposable(offAny);
}
anyEvent({signal} = {}) {
return iterator(this, undefined, {signal});
}
offAny(listener) {
assertListener(listener);
this.logIfDebugEnabled('unsubscribeAny', undefined, undefined);
emitMetaEvent(this, listenerRemoved, {listener});
anyMap.get(this).delete(listener);
}
clearListeners(eventNames) {
eventNames = Array.isArray(eventNames) ? eventNames : [eventNames];
const shouldClearAll = eventNames.some(eventName => !isEventKeyType(eventName));
withSuppressedEnqueue(this, eventNames, () => {
const noError = Symbol('no-error');
let firstError = noError;
try {
for (const eventName of eventNames) {
try {
this.logIfDebugEnabled('clear', eventName, undefined);
} catch (error) {
firstError = firstError === noError ? error : firstError;
}
if (isEventKeyType(eventName)) {
const set = getListeners(this, eventName);
const hadListeners = set?.size > 0;
set?.clear();
finishAndClearProducers(this, eventName);
const lifecycle = hadListeners ? lifecycleMap.get(this).get(eventName) : undefined;
try {
callAndUnsetDeinitFn(lifecycle);
} catch (error) {
firstError = firstError === noError ? error : firstError;
}
} else {
anyMap.get(this).clear();
finishAndClearAllProducers(this);
for (const [eventName, listeners] of eventsMap.get(this).entries()) {
const hadListeners = listeners.size > 0;
listeners.clear();
const lifecycle = hadListeners ? lifecycleMap.get(this).get(eventName) : undefined;
try {
callAndUnsetDeinitFn(lifecycle);
} catch (error) {
firstError = firstError === noError ? error : firstError;
}
// Re-clear in case deinit re-subscribed.
listeners.clear();
eventsMap.get(this).delete(eventName);
}
// Re-clear in case deinit re-subscribed to onAny() or created new iterators.
anyMap.get(this).clear();
finishAndClearAllProducers(this);
}
}
} finally {
if (shouldClearAll) {
anyMap.get(this).clear();
for (const listeners of eventsMap.get(this).values()) {
listeners.clear();
}
eventsMap.get(this).clear();
finishAndClearAllProducers(this);
} else {
// Final re-clear for cross-event deinit re-subscription (e.g., deinit for B re-subscribes to A).
for (const eventName of eventNames) {
if (isEventKeyType(eventName)) {
const set = getListeners(this, eventName);
set?.clear();
eventsMap.get(this).delete(eventName);
finishAndClearProducers(this, eventName);
}
}
}
}
if (firstError !== noError) {
throw firstError;
}
});
}
init(eventName, initFn) {
assertEventName(eventName);
if (isMetaEvent(eventName)) {
throw new TypeError('`eventName` cannot be a meta event');
}
if (typeof initFn !== 'function') {
throw new TypeError('`initFn` must be a function');
}
const lifecycles = lifecycleMap.get(this);
if (lifecycles.has(eventName)) {
throw new Error('`eventName` already has an init function registered');
}
const lifecycle = {initFn, deinitFn: undefined};
lifecycles.set(eventName, lifecycle);
// If listeners already exist, call init immediately
const existingListeners = getListeners(this, eventName);
if (existingListeners?.size > 0) {
try {
const result = initFn();
if (typeof result === 'function') {
lifecycle.deinitFn = result;
}
} catch (error) {
lifecycles.delete(eventName);
throw error;
}
}
return makeDisposable(() => {
try {
callAndUnsetDeinitFn(lifecycle);
} finally {
if (lifecycles.get(eventName) === lifecycle) {
lifecycles.delete(eventName);
}
}
});
}
listenerCount(eventNames) {
eventNames = Array.isArray(eventNames) ? eventNames : [eventNames];
let count = 0;
for (const eventName of eventNames) {
if (isEventKeyType(eventName)) {
count += anyMap.get(this).size
+ (getListeners(this, eventName)?.size ?? 0)
+ (getEventProducers(this, eventName)?.size ?? 0)
+ (getEventProducers(this)?.size ?? 0);
continue;
}
if (eventName !== undefined) {
assertEventName(eventName);
}
count += anyMap.get(this).size;
for (const value of eventsMap.get(this).values()) {
count += value.size;
}
for (const value of producersMap.get(this).values()) {
count += value.size;
}
}
return count;
}
bindMethods(target, methodNames) {
if (!target || typeof target !== 'object') {
throw new TypeError('`target` must be an object');
}
methodNames = defaultMethodNamesOrAssert(methodNames);
for (const methodName of methodNames) {
if (target[methodName] !== undefined) {
throw new Error(`The property \`${methodName}\` already exists on \`target\``);
}
Object.defineProperty(target, methodName, {
enumerable: false,
value: this[methodName].bind(this),
});
}
}
}
const allEmitteryMethods = Object.getOwnPropertyNames(Emittery.prototype).filter(v => v !== 'constructor');
Object.defineProperty(Emittery, 'listenerAdded', {
value: listenerAdded,
writable: false,
enumerable: true,
configurable: false,
});
Object.defineProperty(Emittery, 'listenerRemoved', {
value: listenerRemoved,
writable: false,
enumerable: true,
configurable: false,
});