UNPKG

@hazae41/plume

Version:

Typed async events with sequenced and parallel dispatching

125 lines (121 loc) 4.3 kB
'use strict'; var tslib_es6 = require('../../node_modules/tslib/tslib.es6.cjs'); var box = require('@hazae41/box'); var future = require('@hazae41/future'); var option = require('@hazae41/option'); class SuperEventTarget { #listeners = new Map(); get listeners() { return this.#listeners; } /** * Add a listener to an event * @param type Event type // "abort", "error", "message", "close" * @param listener Event listener // (e) => new Some(123) * @param options Options // { passive: true } * @returns */ on(type, listener, options = {}) { let listeners = this.#listeners.get(type); if (listeners === undefined) { listeners = new Map(); this.#listeners.set(type, listeners); } const off = () => this.off(type, listener); options.signal?.addEventListener("abort", off, { passive: true }); const dispose = () => options.signal?.removeEventListener("abort", off); listeners.set(listener, { ...options, [Symbol.dispose]: () => dispose() }); return off; } /** * Remove a listener from an event * @param type Event type // "abort", "error", "message", "close" * @param listener Event listener // (e) => console.log("hello") * @param options Just to look like DOM's EventTarget * @returns */ off(type, listener) { const env_1 = { stack: [], error: void 0, hasError: false }; try { const listeners = this.#listeners.get(type); if (!listeners) return; const options = tslib_es6.__addDisposableResource(env_1, listeners.get(listener), false); if (!options) return; listeners.delete(listener); if (listeners.size > 0) return; this.#listeners.delete(type); } catch (e_1) { env_1.error = e_1; env_1.hasError = true; } finally { tslib_es6.__disposeResources(env_1); } } /** * Dispatch an event to its listeners * * - Dispatch to active listeners sequencially * - Return if one of the listeners returned something * - Dispatch to passive listeners concurrently * - Return if one of the listeners returned something * - Return nothing * @param params The object to emit * @returns `Some` if the event */ async emit(type, ...params) { const listeners = this.#listeners.get(type); if (!listeners) return new option.None(); const promises = new Array(); for (const [listener, options] of listeners) { if (options.passive) continue; if (options.once) this.off(type, listener); const returned = await listener(...params); if (returned == null) continue; if (returned.isNone()) continue; return new option.Some(returned.get()); } for (const [listener, options] of listeners) { if (!options.passive) continue; if (options.once) this.off(type, listener); const promise = Promise.resolve().then(() => listener(...params)); promises.push(promise); continue; } const returneds = await Promise.all(promises); for (const returned of returneds) { if (returned == null) continue; if (returned.isNone()) continue; return new option.Some(returned.get()); } return new option.None(); } /** * Like `.on`, but instead of returning to the target, capture the returned value in a future, and return nothing to the target * @param type * @param callback * @returns */ wait(type, callback) { const future$1 = new future.Future(); const dispose = this.on(type, async (...params) => { return await callback(future$1, ...params); }, { passive: true }); return box.Pin.with(future$1.promise, dispose); } } exports.SuperEventTarget = SuperEventTarget; //# sourceMappingURL=target.cjs.map