event-bus.js in JavaScript
A typed-ish event bus: subscribe, emit, unsubscribe, and await one event.
#!/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);
How it works
- Handlers live in a Map of Sets, so removal is cheap.
onceresolves a promise, which composes with await.- A handler that throws must not stop the others.
Keywords and builtins used here
MapPromiseSetawaitcatchclassconstforifletofreturnthisthrowtry
The run, in numbers
- Lines
- 77
- Characters to type
- 1902
- Tokens
- 586
- Three-star pace
- 110 tpm
At the three-star pace of 110 tokens a minute, this run takes about 320 seconds.
Step 1 of 1 in Encore, step 13 of 13 in The browser.