123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531 |
- 'use strict';
- const {anyMap, producersMap, eventsMap} = require('./maps.js');
- const anyProducer = Symbol('anyProducer');
- const resolvedPromise = Promise.resolve();
- // Define symbols for "meta" events.
- const listenerAdded = Symbol('listenerAdded');
- const listenerRemoved = Symbol('listenerRemoved');
- let canEmitMetaEvents = false;
- let isGlobalDebugEnabled = false;
- function assertEventName(eventName) {
- if (typeof eventName !== 'string' && typeof eventName !== 'symbol' && typeof eventName !== 'number') {
- 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 = typeof eventName === 'string' || typeof eventName === 'symbol' || typeof eventName === 'number' ? eventName : anyProducer;
- const producers = producersMap.get(instance);
- if (!producers.has(key)) {
- return;
- }
- 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) {
- let set = getEventProducers(instance, eventName);
- if (!set) {
- set = new Set();
- const producers = producersMap.get(instance);
- producers.set(eventName, set);
- }
- set.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) {
- const set = getEventProducers(instance, eventName);
- if (set) {
- set.delete(producer);
- if (set.size === 0) {
- const producers = producersMap.get(instance);
- producers.delete(eventName);
- }
- }
- }
- 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 isMetaEvent = eventName => eventName === listenerAdded || eventName === listenerRemoved;
- function emitMetaEvent(emitter, eventName, eventData) {
- if (isMetaEvent(eventName)) {
- try {
- canEmitMetaEvents = true;
- emitter.emit(eventName, eventData);
- } finally {
- canEmitMetaEvents = false;
- }
- }
- }
- 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;
- };
- }
- static get isDebugEnabled() {
- if (typeof process !== 'object') {
- return isGlobalDebugEnabled;
- }
- const {env} = 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());
- producersMap.get(this).set(anyProducer, new Set());
- this.debug = options.debug || {};
- if (this.debug.enabled === undefined) {
- this.debug.enabled = false;
- }
- if (!this.debug.logger) {
- 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) {
- assertListener(listener);
- eventNames = Array.isArray(eventNames) ? eventNames : [eventNames];
- for (const eventName of eventNames) {
- assertEventName(eventName);
- let set = getListeners(this, eventName);
- if (!set) {
- set = new Set();
- const events = eventsMap.get(this);
- events.set(eventName, set);
- }
- set.add(listener);
- this.logIfDebugEnabled('subscribe', eventName, undefined);
- if (!isMetaEvent(eventName)) {
- emitMetaEvent(this, 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);
- const set = getListeners(this, eventName);
- if (set) {
- set.delete(listener);
- if (set.size === 0) {
- const events = eventsMap.get(this);
- events.delete(eventName);
- }
- }
- this.logIfDebugEnabled('unsubscribe', eventName, undefined);
- if (!isMetaEvent(eventName)) {
- emitMetaEvent(this, listenerRemoved, {eventName, listener});
- }
- }
- }
- once(eventNames) {
- let off_;
- const promise = new Promise(resolve => {
- off_ = this.on(eventNames, data => {
- off_();
- resolve(data);
- });
- });
- promise.off = off_;
- return promise;
- }
- 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);
- if (isMetaEvent(eventName) && !canEmitMetaEvents) {
- throw new TypeError('`eventName` cannot be meta event `listenerAdded` or `listenerRemoved`');
- }
- this.logIfDebugEnabled('emit', eventName, eventData);
- enqueueProducers(this, eventName, eventData);
- const listeners = getListeners(this, eventName) || new Set();
- const anyListeners = anyMap.get(this);
- const staticListeners = [...listeners];
- const staticAnyListeners = isMetaEvent(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);
- if (isMetaEvent(eventName) && !canEmitMetaEvents) {
- throw new TypeError('`eventName` cannot be meta event `listenerAdded` or `listenerRemoved`');
- }
- this.logIfDebugEnabled('emitSerial', eventName, eventData);
- const listeners = getListeners(this, eventName) || new Set();
- 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);
- this.logIfDebugEnabled('subscribeAny', undefined, undefined);
- anyMap.get(this).add(listener);
- emitMetaEvent(this, listenerAdded, {listener});
- return this.offAny.bind(this, listener);
- }
- anyEvent() {
- return iterator(this);
- }
- 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];
- for (const eventName of eventNames) {
- this.logIfDebugEnabled('clear', eventName, undefined);
- if (typeof eventName === 'string' || typeof eventName === 'symbol' || typeof eventName === 'number') {
- const set = getListeners(this, eventName);
- if (set) {
- set.clear();
- }
- const producers = getEventProducers(this, eventName);
- if (producers) {
- for (const producer of producers) {
- producer.finish();
- }
- producers.clear();
- }
- } else {
- anyMap.get(this).clear();
- for (const [eventName, listeners] of eventsMap.get(this).entries()) {
- listeners.clear();
- eventsMap.get(this).delete(eventName);
- }
- for (const [eventName, producers] of producersMap.get(this).entries()) {
- for (const producer of producers) {
- producer.finish();
- }
- producers.clear();
- producersMap.get(this).delete(eventName);
- }
- }
- }
- }
- 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) || new Set()).size +
- (getEventProducers(this, eventName) || new Set()).size + (getEventProducers(this) || new Set()).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');
- 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;
|