Events
A type-safe event system with multiple emitter patterns.
Installation
- Yarn
- pnpm
- npm
yarn add @boost/event
pnpm add @boost/event
npm install @boost/event
Events
The event system is built around individual Event
classes that can be instantiated in
isolation, register and unregister their own listeners, and emit values by executing each listener
with arguments. There are multiple types of events, so choose the best one for each
use case.
To begin using events, instantiate an Event
with a unique name -- the name is purely for
debugging purposes.
import { Event } from '@boost/event';
const event = new Event<[string]>('example');
Event
s utilize TypeScript generics for typing the arguments passed to listener functions.
This can be defined using a tuple or an array in the 1st generic slot. The 2nd slot is reserved for
scopes.
// One argument of type number
new Event<[number]>('foo');
// Two arguments of type number and string
new Event<[number, string]>('bar');
// Three arguments with the last item being optional
new Event<[object, boolean, string?]>('baz');
// Array of any type or size
new Event<unknown[]>('foo');
Registering listeners
Listeners are simply functions that can be registered to an event using Event#listen()
.
The same listener function reference will only be registered once.
event.listen(listener);
A listener can also be registered to execute only once, using Event#once()
, regardless of
how many times the event has been emitted.
event.once(listener);
Unregistering listeners
A listener can be unregistered from an event using Event#unlisten()
. The same listener
reference used to register must also be used for unregistering.
event.unlisten(listener);
To make this flow easier, a pre-configured unlistener is returned from Event#listen()
.
const unlisten = event.listen(listener);
unlisten();
Emitting events
Emitting is the concept of executing all registered listeners with a set of arguments. This can be
achieved through the Event#emit()
method, which requires an array of values to pass to
each listener as arguments.
event.emit(['abc']);
The array values and its types should match the generics defined on the constructor.
Scopes
Scopes are a mechanism for restricting listeners to a unique subset. Scopes are defined as the 2nd
argument to Event#listen()
, #unlisten()
, #once()
, and
#emit()
.
event.listen(listener);
event.listen(listener, 'foo');
event.listen(listener, 'bar');
// Will only execute the 1st listener
event.emit([]);
// Will only execute the 2nd listener
event.emit([], 'foo');
A list of acceptable scope names can be passed as the 2nd generic slot to Event
,
otherwise all strings are allowed.
new Event<[number], 'foo' | 'bar' | 'baz'>('event');
Event types
There are 4 types of events that can be instantiated and emitted.
Event
Standard Event
that executes listeners in the order they were registered.
import { Event } from '@boost/event';
const event = new Event<[string, number]>('standard');
event.listen(listener);
event.emit(['abc', 123]);
BailEvent
The BailEvent
is like Event
but can bail the execution
loop early if a listener returns false
. The emit method will return true
if a bail occurs.
import { BailEvent } from '@boost/event';
const event = new BailEvent<[object]>('bail');
// Will execute
event.listen(() => {});
// Will execute and bail
event.listen(() => false);
// Will not execute
event.listen(() => {});
const bailed = event.emit([{ example: true }]);
ConcurrentEvent
The ConcurrentEvent
executes listeners in parallel and returns
a promise with the result of all listeners.
import { ConcurrentEvent } from '@boost/event';
const event = new ConcurrentEvent<[]>('parallel');
event.listen(doHeavyProcess);
event.listen(doBackgroundJob);
// Async/await
const result = await event.emit([]);
// Promise
event.emit([]).then((results) => {});
WaterfallEvent
The WaterfallEvent
executes each listener in order, passing the
previous listeners return value as an argument to the next listener. The final value is then
returned as a result.
import { WaterfallEvent } from '@boost/event';
const event = new WaterfallEvent<number>('waterfall');
event.listen((num) => num * 2);
event.listen((num) => num * 3);
const result = event.emit(10); // 60
This event only accepts a single argument. The generic type should not be an array, as it types the only argument and the return type.