typestar

event-bus.js en JavaScript

Un bus de eventos casi tipado: suscribir, emitir, cancelar y esperar un evento.

#!/usr/bin/env node

class EventBus {
  #handlers = new Map();

  on(type, handler) {
    if (!this.#handlers.has(type)) this.#handlers.set(type, new Set());
    this.#handlers.get(type).add(handler);
    return () => this.off(type, handler);
  }

  off(type, handler) {
    const handlers = this.#handlers.get(type);
    if (!handlers) return false;
    const removed = handlers.delete(handler);
    if (handlers.size === 0) this.#handlers.delete(type);
    return removed;
  }

  once(type, { signal } = {}) {
    const { promise, resolve, reject } = Promise.withResolvers();
    const stop = this.on(type, (payload) => {
      stop();
      resolve(payload);
    });
    signal?.addEventListener("abort", () => {
      stop();
      reject(signal.reason ?? new Error("aborted"));
    }, { once: true });
    return promise;
  }

  emit(type, payload) {
    const handlers = this.#handlers.get(type);
    if (!handlers) return 0;

    let delivered = 0;
    for (const handler of [...handlers]) {
      try {
        handler(payload);
        delivered += 1;
      } catch (error) {
        this.emit("error", new Error(`handler for ${type} threw`,
                                     { cause: error }));
      }
    }
    return delivered;
  }

  get types() {
    return [...this.#handlers.keys()];
  }
}

const bus = new EventBus();
const seen = [];

const stopRuns = bus.on("run", (payload) => seen.push(payload.tpm));
bus.on("run", () => { throw new Error("bad handler"); });
bus.on("error", (error) => seen.push(`error: ${error.cause.message}`));

console.log("delivered:", bus.emit("run", { tpm: 104 }));
console.log("types:", bus.types.sort());

stopRuns();
console.log("after unsubscribe:", bus.emit("run", { tpm: 98 }));

setTimeout(() => bus.emit("cleared", { set: "Async" }), 5);
const cleared = await bus.once("cleared");
console.log("awaited:", cleared.set);

const controller = new AbortController();
setTimeout(() => controller.abort(new Error("gave up")), 5);
const failed = await bus.once("never", { signal: controller.signal })
  .catch((error) => error.message);

console.log(failed, seen);

Cómo funciona

  1. Los manejadores viven en un Map de Sets, así quitar es barato.
  2. once resuelve una promesa, que se compone con await.
  3. Un manejador que lanza no debe frenar a los demás.

Palabras clave y builtins usados aquí

El intento, en números

Líneas
77
Caracteres a escribir
1902
Tokens
586
Ritmo de tres estrellas
110 tpm

Al ritmo de tres estrellas de 110 tokens por minuto, este intento toma unos 320 segundos.

Escribe este fragmento

Paso 1 de 1 en Bis; paso 13 de 13 en El navegador.

← Anterior