Event Emitter With Once And Off Semantics

A small event emitter that adds one-shot listeners, removal by original callback, snapshot dispatch and listener counts without Node dependencies.

JavascriptCli Tooling

Code

snippet
export class EventEmitter {
  #listeners = new Map();

  on(event, listener) {
    if (typeof event !== "string" || event.length === 0) {
      throw new TypeError("event must be a non-empty string");
    }
    if (typeof listener !== "function") {
      throw new TypeError("listener must be a function");
    }
    let bucket = this.#listeners.get(event);
    if (!bucket) {
      bucket = new Set();
      this.#listeners.set(event, bucket);
    }
    bucket.add(listener);
    return () => this.off(event, listener);
  }

  once(event, listener) {
    const wrapper = (...args) => {
      this.off(event, wrapper);
      listener(...args);
    };
    wrapper.original = listener;
    return this.on(event, wrapper);
  }

  off(event, listener) {
    const bucket = this.#listeners.get(event);
    if (!bucket) return false;
    let removed = false;
    for (const entry of bucket) {
      if (entry === listener || entry.original === listener) {
        bucket.delete(entry);
        removed = true;
      }
    }
    if (bucket.size === 0) this.#listeners.delete(event);
    return removed;
  }

  emit(event, ...args) {
    const bucket = this.#listeners.get(event);
    if (!bucket) return false;
    for (const listener of [...bucket]) listener(...args);
    return true;
  }

  listenerCount(event) {
    return this.#listeners.get(event)?.size ?? 0;
  }

  removeAllListeners(event) {
    if (event === undefined) this.#listeners.clear();
    else this.#listeners.delete(event);
  }
}