@boost/event
Version:
An event system with multiple emitter patterns.
165 lines (154 loc) • 4.84 kB
JavaScript
// 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