@hazae41/plume
Version:
Typed async events with sequenced and parallel dispatching
125 lines (121 loc) • 4.3 kB
JavaScript
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
;