123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415 |
- 'use strict';
- const anyMap = new WeakMap();
- const eventsMap = new WeakMap();
- const producersMap = new WeakMap();
- const anyProducer = Symbol('anyProducer');
- const resolvedPromise = Promise.resolve();
- const listenerAdded = Symbol('listenerAdded');
- const listenerRemoved = Symbol('listenerRemoved');
- function assertEventName(eventName) {
- if (typeof eventName !== 'string' && typeof eventName !== 'symbol') {
- throw new TypeError('eventName must be a string or a symbol');
- }
- }
- 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)) {
- events.set(eventName, new Set());
- }
- return events.get(eventName);
- }
- function getEventProducers(instance, eventName) {
- const key = typeof eventName === 'string' || typeof eventName === 'symbol' ? eventName : anyProducer;
- const producers = producersMap.get(instance);
- if (!producers.has(key)) {
- producers.set(key, new Set());
- }
- return producers.get(key);
- }
- function enqueueProducers(instance, eventName, eventData) {
- const producers = producersMap.get(instance);
- if (producers.has(eventName)) {
- for (const producer of producers.get(eventName)) {
- producer.enqueue(eventData);
- }
- }
- if (producers.has(anyProducer)) {
- const item = Promise.all([eventName, eventData]);
- for (const producer of producers.get(anyProducer)) {
- producer.enqueue(item);
- }
- }
- }
- function iterator(instance, eventNames) {
- eventNames = Array.isArray(eventNames) ? eventNames : [eventNames];
- let isFinished = false;
- let flush = () => {};
- let queue = [];
- const producer = {
- enqueue(item) {
- queue.push(item);
- flush();
- },
- finish() {
- isFinished = true;
- flush();
- }
- };
- for (const eventName of eventNames) {
- getEventProducers(instance, eventName).add(producer);
- }
- return {
- async next() {
- if (!queue) {
- return {done: true};
- }
- if (queue.length === 0) {
- if (isFinished) {
- queue = undefined;
- return this.next();
- }
- await new Promise(resolve => {
- flush = resolve;
- });
- return this.next();
- }
- return {
- done: false,
- value: await queue.shift()
- };
- },
- async return(value) {
- queue = undefined;
- for (const eventName of eventNames) {
- getEventProducers(instance, eventName).delete(producer);
- }
- flush();
- return arguments.length > 0 ?
- {done: true, value: await value} :
- {done: true};
- },
- [Symbol.asyncIterator]() {
- return this;
- }
- };
- }
- 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 isListenerSymbol = symbol => symbol === listenerAdded || symbol === listenerRemoved;
- class Emittery {
- static mixin(emitteryPropertyName, methodNames) {
- methodNames = defaultMethodNamesOrAssert(methodNames);
- return target => {
- 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;
- };
- }
- constructor() {
- anyMap.set(this, new Set());
- eventsMap.set(this, new Map());
- producersMap.set(this, new Map());
- }
- on(eventNames, listener) {
- assertListener(listener);
- eventNames = Array.isArray(eventNames) ? eventNames : [eventNames];
- for (const eventName of eventNames) {
- assertEventName(eventName);
- getListeners(this, eventName).add(listener);
- if (!isListenerSymbol(eventName)) {
- this.emit(listenerAdded, {eventName, listener});
- }
- }
- return this.off.bind(this, eventNames, listener);
- }
- off(eventNames, listener) {
- assertListener(listener);
- eventNames = Array.isArray(eventNames) ? eventNames : [eventNames];
- for (const eventName of eventNames) {
- assertEventName(eventName);
- getListeners(this, eventName).delete(listener);
- if (!isListenerSymbol(eventName)) {
- this.emit(listenerRemoved, {eventName, listener});
- }
- }
- }
- once(eventNames) {
- return new Promise(resolve => {
- const off = this.on(eventNames, data => {
- off();
- resolve(data);
- });
- });
- }
- events(eventNames) {
- eventNames = Array.isArray(eventNames) ? eventNames : [eventNames];
- for (const eventName of eventNames) {
- assertEventName(eventName);
- }
- return iterator(this, eventNames);
- }
- async emit(eventName, eventData) {
- assertEventName(eventName);
- enqueueProducers(this, eventName, eventData);
- const listeners = getListeners(this, eventName);
- const anyListeners = anyMap.get(this);
- const staticListeners = [...listeners];
- const staticAnyListeners = isListenerSymbol(eventName) ? [] : [...anyListeners];
- await resolvedPromise;
- await Promise.all([
- ...staticListeners.map(async listener => {
- if (listeners.has(listener)) {
- return listener(eventData);
- }
- }),
- ...staticAnyListeners.map(async listener => {
- if (anyListeners.has(listener)) {
- return listener(eventName, eventData);
- }
- })
- ]);
- }
- async emitSerial(eventName, eventData) {
- assertEventName(eventName);
- const listeners = getListeners(this, eventName);
- const anyListeners = anyMap.get(this);
- const staticListeners = [...listeners];
- const staticAnyListeners = [...anyListeners];
- await resolvedPromise;
- /* eslint-disable no-await-in-loop */
- for (const listener of staticListeners) {
- if (listeners.has(listener)) {
- await listener(eventData);
- }
- }
- for (const listener of staticAnyListeners) {
- if (anyListeners.has(listener)) {
- await listener(eventName, eventData);
- }
- }
- /* eslint-enable no-await-in-loop */
- }
- onAny(listener) {
- assertListener(listener);
- anyMap.get(this).add(listener);
- this.emit(listenerAdded, {listener});
- return this.offAny.bind(this, listener);
- }
- anyEvent() {
- return iterator(this);
- }
- offAny(listener) {
- assertListener(listener);
- this.emit(listenerRemoved, {listener});
- anyMap.get(this).delete(listener);
- }
- clearListeners(eventNames) {
- eventNames = Array.isArray(eventNames) ? eventNames : [eventNames];
- for (const eventName of eventNames) {
- if (typeof eventName === 'string' || typeof eventName === 'symbol') {
- getListeners(this, eventName).clear();
- const producers = getEventProducers(this, eventName);
- for (const producer of producers) {
- producer.finish();
- }
- producers.clear();
- } else {
- anyMap.get(this).clear();
- for (const listeners of eventsMap.get(this).values()) {
- listeners.clear();
- }
- for (const producers of producersMap.get(this).values()) {
- for (const producer of producers) {
- producer.finish();
- }
- producers.clear();
- }
- }
- }
- }
- listenerCount(eventNames) {
- eventNames = Array.isArray(eventNames) ? eventNames : [eventNames];
- let count = 0;
- for (const eventName of eventNames) {
- if (typeof eventName === 'string') {
- count += anyMap.get(this).size + getListeners(this, eventName).size +
- getEventProducers(this, eventName).size + getEventProducers(this).size;
- continue;
- }
- if (typeof 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 (typeof target !== 'object' || target === null) {
- 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');
- // Subclass used to encourage TS users to type their events.
- Emittery.Typed = class extends Emittery {};
- Object.defineProperty(Emittery.Typed, 'Typed', {
- enumerable: false,
- value: undefined
- });
- Object.defineProperty(Emittery, 'listenerAdded', {
- value: listenerAdded,
- writable: false,
- enumerable: true,
- configurable: false
- });
- Object.defineProperty(Emittery, 'listenerRemoved', {
- value: listenerRemoved,
- writable: false,
- enumerable: true,
- configurable: false
- });
- module.exports = Emittery;
|