UNPKG

@boost/event

Version:

An event system with multiple emitter patterns.

165 lines (154 loc) 4.84 kB
// Bundled with Packemon: https://packemon.dev // Platform: browser, Support: stable, Format: esm import { createInternalDebugger, createScopedError } from '@boost/internal'; const WILDCARD_SCOPE = '*'; const EVENT_NAME_PATTERN = /^[a-z]{1}[-.a-z0-9]*[a-z]{1}$/u; const debug = createInternalDebugger('event'); const errors = { LISTENER_INVALID: 'Invalid event listener for "{0}", must be a function.', NAME_INVALID: 'Invalid event {0} "{1}". May only contain dashes, periods, and lowercase characters.' }; const EventError = createScopedError('EVT', 'EventError', errors); class BaseEvent { constructor(name) { this.listeners = new Map(); this.name = void 0; this.name = this.validateName(name, 'name'); if (process.env.NODE_ENV !== "production") { debug('New %S created: %s', this.constructor.name, name); } } /** * Remove all listeners from the event. */ clearListeners(scope) { if (scope) { this.getListeners(scope).clear(); } else { this.listeners.clear(); } return this; } /** * Return a set of listeners for a specific event scope. */ getListeners(scope) { const key = this.validateName(scope ?? WILDCARD_SCOPE, 'scope'); if (!this.listeners.has(key)) { this.listeners.set(key, new Set()); } return this.listeners.get(key); } /** * Return a list of all scopes with listeners. */ getScopes() { return [...this.listeners.keys()]; } /** * Register a listener to the event. */ listen(listener, scope) { if (process.env.NODE_ENV !== "production") { debug('Registering "%s" listener', this.name); } this.getListeners(scope).add(this.validateListener(listener)); return () => { this.unlisten(listener, scope); }; } /** * Register a listener to the event that only triggers once. */ once(listener, scope) { const func = this.validateListener(listener); const handler = (...args) => { this.unlisten(handler); return func(...args); }; return this.listen(handler, scope); } /** * Remove a listener from the event. */ unlisten(listener, scope) { if (process.env.NODE_ENV !== "production") { debug('Unregistering "%s" listener', this.name); } this.getListeners(scope).delete(listener); return this; } /** * Validate the listener is a function. */ validateListener(listener) { if (process.env.NODE_ENV !== "production" && typeof listener !== 'function') { throw new EventError('LISTENER_INVALID', [this.name]); } return listener; } /** * Validate the name/scope match a defined pattern. */ validateName(name, type) { if (type === 'scope' && name === WILDCARD_SCOPE) { return name; } if (process.env.NODE_ENV !== "production" && !name.match(EVENT_NAME_PATTERN)) { throw new EventError('NAME_INVALID', [type, name]); } return name; } /** * Emit the event by executing all scoped listeners with the defined arguments. */ } class BailEvent extends BaseEvent { /** * Synchronously execute listeners with the defined arguments. * If a listener returns `false`, the loop with be aborted early, * and the emitter will return `true` (for bailed). */ emit(args, scope) { if (process.env.NODE_ENV !== "production") { debug('Emitting "%s%s" as bail', this.name, scope ? `:${scope}` : ''); } return [...this.getListeners(scope)].some(listener => listener(...args) === false); } } class ConcurrentEvent extends BaseEvent { /** * Asynchronously execute listeners for with the defined arguments. * Will return a promise with an array of each listener result. */ async emit(args, scope) { if (process.env.NODE_ENV !== "production") { debug('Emitting "%s%s" as concurrent', this.name, scope ? `:${scope}` : ''); } return Promise.all([...this.getListeners(scope)].map(listener => listener(...args))); } } class Event extends BaseEvent { /** * Synchronously execute listeners with the defined arguments. */ emit(args, scope) { [...this.getListeners(scope)].forEach(listener => { listener(...args); }); } } class WaterfallEvent extends BaseEvent { /** * Synchronously execute listeners with the defined argument value. * The return value of each listener will be passed as an argument to the next listener. */ emit(arg, scope) { if (process.env.NODE_ENV !== "production") { debug('Emitting "%s%s" as waterfall', this.name, scope ? `:${scope}` : ''); } return [...this.getListeners(scope)].reduce((nextValue, listener) => listener(nextValue), arg); } } export { BailEvent, BaseEvent, ConcurrentEvent, EVENT_NAME_PATTERN, Event, EventError, WILDCARD_SCOPE, WaterfallEvent }; //# sourceMappingURL=index.js.map