Atvik is an event emitter for JavaScript and TypeScript. This library provides emitters for individual events that work well with types and inheritance.
import { Event } from 'atvik';
// Create an event
const event = new Event(thisValueForListeners);
// Subscribe to the event
const handle = event.subscribe((arg1) => console.log('event', arg1));
// Emit the event, triggering all listeners
event.emit('first argument');
// Unsubscribe from the event
handle.unsubscribe();
// Using for await ... of listeners
for await (const [ arg1 ] of event) {
console.log('event', arg1);
}
// Public API without emit is available
const subscribable = event.subscribable;
subscribable((arg1) => console.log('event', arg1));
for await (const [ arg1 ] of subscribable) {
console.log('event', arg1);
}
Events come with a public API called Subscribable
for use with classes so
that users of a class can only subscribe to events and not emit them.
class Counter {
constructor() {
this.countUpdatedEvent = new Event(this);
this.count = 0;
}
get onCountUpdated() {
/*
* Return the subscribable of the event - which is a function that can be
* used to listen to the event.
*/
return this.countUpdatedEvent.subscribable;
}
increment() {
this.count++;
this.countUpdatedEvent.emit(this.count);
}
}
const counter = new Counter();
// Subscribe to the event with a listener
counter.onCountUpdated(currentCount => console.log(currentCount));
// Increment and trigger the countUpdated event
counter.increment();
Subscribable
is a function that can be used to directly subscribe a listener,
but can also be used for more advanced use cases. The following functions are
supported:
subscribe(listener: Listener): SubscriptionHandle
- Subscribe a listener, the same as invoking the function directlyunsubscribe(listener: Listener): void
- Unsubscribe a listeneronce(): Promise
- Create a promise that will resolve once the event is emittedfilter(filter: (...args) => boolean)
- Filter the subscribable, returning an upwithThis(newThis)
- Change the this used for listeners
Atvik is compatible with TypeScript and provides a type-safe interface to listen to and emit events:
import { Event } from 'atvik';
const parent = {};
// Create an event without any expected arguments
const noArgEvent = new Event<object>(parent);
// Subscribing will be checked so it takes in zero arguments
noArgEvent.subscribe(() => /* do stuff here */);
// Emitting the event can only be done without any parameters
noArgEvent.emit();
Events can have arguments that will be checked in the listeners and when emitting:
// Pass a second type in array form to specify the expected arguments
const argEvent = new Event<object, [ number ]>(parent);
// Subscribe will now check that the arguments are compatible
argEvent.subscribe((count) => /* do stuff here */);
// Emitting the event now requires arguments to be passed
argEvent.emit(10);
Listening for a single event can be done via promises:
// Wait for the event to be emitted
const args = await event.once();
// Or using the subscribable
const args = await event.subscribable.once();
In some cases it might be useful to filter events without managing a separate
Event
. Atvik supports creating a filtered Subscribable
for this purpose:
const event = new Event(thisValueForListener);
const onlyEvenNumbers = event.filter((arg1) => arg1 % 2 === 0);
onlyEvenNumbers(number => console.log('Got number:', number));
// Will not invoke listener added via onlyEvenNumbers
event.emit(1);
// This will invoke the listener
event.emit(2);
Events and subscribables can be iterated over using a for await .. of
loop,
allowing for the creation of simple event loops:
for await (const [ arg1 ] of event) {
console.log('event', arg1)
}
Sometimes events are emitted faster than they can be consumed, limiting and controlling overflow of events can be done via {@link iterator}.
As an example this will limit to 10 queued events and then start dropping the earliest ones:
for await (const [ arg1 ] of subscribable.iterator({ limit: 10 })) {
console.log('event', arg1);
}
The behavior to use when the queue is full can be controlled by setting the OverflowBehavior:
const iteratorOptions = {
limit: 10,
overflowBehavior: OverflowBehavior.DropNewest
};
for await (const [ arg1 ] of subscribable.iterator(iteratorOptions)) {
console.log('event', arg1);
}
For some use cases it is necessary to monitor if an event has any listeners,
for this library provides the monitorListeners
function. If a monitor is
registered it will be invoked for any change in listeners, so subscribing or
unsubscribing will always trigger the monitor.
Example with a fictional service being started and stopped:
event.monitorListeners(theEvent => {
if(theEvent.hasListeners) {
// The event has at least one active listener
if(! service.started) {
service.start();
}
} else {
// No active listeners
if(service.started) {
service.stop();
}
}
});
Only a single monitor may be active at a time and the active monitor can be
removed via removeMonitor()
.
AsyncSubscribable
is a variant of Subscribable
where listeners are
subscribed in an asynchronous way. It is intended for use when listeners need
some asynchronous action before they are available, such as a remote RPC
scenario. The API of AsyncSubscribable
matches Subscribable
but returns
promises for subscribe
, unsubscribe
and emit
:
// Subscribe to the event
const handle = await asyncSubscribable.subscribe((arg1) => /* do stuff here */);
// Unsubscribe from the event
await handle.unsubscribe();
An implementation can be created via createAsyncSubscribable
to create a
bridge to something like a remote service, or via AsyncEvent
for local use.
Using createAsyncSubscribable
:
import { createAsyncSubscribable } from 'atvik';
const asyncSubscribable = createAsyncSubscribable({
subscribe: async (listener) => {
// Subscribe listener here
},
unsubscribe: async (listener) => {
// Unsubscribe listener here
return listenerWasSubscribed;
}
});
Using AsyncEvent
:
import { AsyncEvent } from 'atvik';
const event = new AsyncEvent(thisValueForListeners);
// Emit the event, triggering all listeners
await event.emit('first argument');
Support is included for adapting some common event emitters such as Nodes
EventEmitter
and DOM events using createEventAdapter.
import { createEventAdapter } from 'atvik';
const subscribable = createEventAdapter(eventEmitter, 'nameOfEvent');
// Use subscribable as normal
subscribable(arg1 => console.log('event', arg1));