Source: event_emitter.js

import { v4 as uuid } from '../node_modules/uuid/dist/esm-browser/index.js';

/**
 * Event Emitter
 */

class EventEmitter {

    constructor() {
        this._events = {};
    }

    addEvent(eventName) {
        if (!this._events[eventName]) {
            this._events[eventName] = {};
        }
    }

    on(eventName, listener, id) {
        if (!this._events[eventName]) {
            // throw new Error(`Subscribing to unknown event ${this.constructor.name}#${eventName}`)
            this._events[eventName] = {};
        }

        const listenerId = id || uuid();
        
        this._events[eventName][listenerId] = listener;
        
        if (this._events.listenerChange) {
            this.emit('listenerChange',{event: eventName, listenerObject: this._events[eventName]});
        }
    }

    once(eventName,listener) {

        const listenerId = uuid();

        this.on(
            eventName,
            (...data) => {
                this.removeListener(eventName,listenerId);
                listener(...data);
            },
            listenerId
        );
    }

    hasListener(eventName, id) {
        if ( ! this._events) {
            // console.log('Event object destroyed', this.label, this);
            return false;
        }
        return this._events[eventName] && this._events[eventName][id];
    }

    removeListener(eventName, id) {
        if (!this._events[eventName]) {
            throw new Error(`Can't remove listener for unknown event "${eventName}".`);
        }
        if (!this._events[eventName][id]) {
            throw new Error(`Can't remove ${eventName} listener with unknown id "${id}".`);
        }

        delete this._events[eventName][id];

        if (this._events.listenerChange) {
            this.emit('listenerChange',{event: eventName, listenerObject: this._events[eventName]});
        }

        // const filterListeners = (listener) => listener !== listenerToRemove;

        // this._events[name] = this._events[name].filter(filterListeners);
    }

    
    /**
     * @param {string} eventName 
     * @param {any} data 
     * @returns {Promise<any>}
     */

    emit(eventName, data) {

        if (!this._events[eventName]) {
            // console.warn('create in emit', eventName)
            this._events[eventName] = {};
        }

        return Promise.all(
            Object.values(this._events[eventName]).map(eventHandler => eventHandler(data))
        );
    }

    removeAllListeners() {
        for ( let [ evt, listeners] of Object.entries( this._events || {}) ) {
            for ( let [ id, listener ]  of Object.entries( listeners ) ) {
                this.removeListener(evt, id);
            }
        }
    }

    destroy() {
        this.removeAllListeners();
        if ( typeof super.destroy === 'function' ) {
            super.destroy()
        }
        // destroy everything
        // this class doesn't extend anything
        for ( let prop in this ) {
            this[ prop ] = null;
        }
    }
}

export { EventEmitter };