Skip to main content

EventBus usage

The eridu-tech/event-bus component provides a way for dispatching and listening to events independent of underlying technology.

Initial configuration

To begin using the EventBus class, you'll need to create and configure an instance:

import { MemoryEventBusAdapter } from "eridu-tech/event-bus/memory-event-bus-adapter";
import type { IEventBus } from "eridu-tech/event-bus/contracts";
import { EventBus } from "eridu-tech/event-bus";

const eventBus: IEventBus = new EventBus({
// You can choose the adapter to use
adapter: new MemoryEventBusAdapter(),
});
info

Here is a complete list of settings for the EventBus class.

Event handling basics

Registering Listeners and Dispatching Events

Event listeners can be added to respond to specific events:

await eventBus.addListener("add", (event) => {
console.log(event);
});

await eventBus.dispatch("add", {
a: 5,
b: 5,
});

Listener management

To properly remove a listener, you must use a named function:

import type { BaseEvent } from "eridu-tech/event-bus/contracts";

const listener = (event: BaseEvent) => {
console.log(event);
};

await eventBus.addListener("add", listener);

await eventBus.removeListener("add", listener);

// The listener is removed before dispatch and won't be triggered.
await eventBus.dispatch("add", {
a: 5,
b: 5,
});

Patterns

Compile time type safety

An event map can be used to strictly type the events:

import { MemoryEventBusAdapter } from "eridu-tech/event-bus/memory-event-bus-adapter";
import type { IEventBus } from "eridu-tech/event-bus/contracts";
import { EventBus } from "eridu-tech/event-bus";

type AddEvent = {
a: number;
b: number;
};

type EventMap = {
add: AddEvent;
};

const eventBus = new EventBus<EventMap>({
adapter: new MemoryEventBusAdapter(),
});

// A typescript error will show up because the event name doesnt exist.
await eventBus.dispatch("addd", {
a: 2,
b: 2,
});

// A typescript error will show up because the event fields doesnt match
await eventBus.dispatch("add", {
nbr1: 1,
nbr2: 2,
});

// A typescript error will show up because the event name doesnt exist.
await eventBus.addListener("addd", (event) => {
console.log(event);
});

Subscribe method

The subscription pattern provides automatic cleanup through an unsubscribe function:

const unsubscribe = await eventBus.subscribe("add", (event) => {
console.log(event);
});
await eventBus.dispatch("add", {
a: 20,
b: 5,
});
await unsubscribe();

One-Time event handling

For listeners that should only trigger once:

await eventBus.listenOnce("add", (event) => {
console.log(event);
});

// Listener will be only triggered here
await eventBus.dispatch("add", {
a: 5,
b: 5,
});

// Listener will not be triggered because it removed after the first dispatch.
await eventBus.dispatch("add", {
a: 3,
b: 3,
});

You can also cancel one-time listeners before they trigger:

import type { BaseEvent } from "eridu-tech/event-bus/contracts";

const listener = (event: BaseEvent) => {
console.log(event);
};

await eventBus.listenOnce("add", listener);

await eventBus.removeListener("add", listener);

// The listener is removed before dispatch and won't be triggered.
await eventBus.dispatch("add", {
a: 5,
b: 5,
});

The subscribeOnce method creates a one-time listener and returns an unsubscribe function:

const unsubscribe = await eventBus.subscribeOnce("add", (event) => {
console.log(event);
});

await unsubscribe();

await eventBus.dispatch("add", {
a: 5,
b: 5,
});

Promise-based event handling

Wait for events using promises:

import { delay } from "eridu-tech/utilities";
import { TimeSpan } from "eridu-tech/time-span";

// Register the promise before dispatching the event.
const eventPromise = eventBus.asPromise("add");

await delay(TimeSpan.fromSeconds(1));
await eventBus.dispatch("add", {
a: 30,
b: 20,
});

const event = await eventPromise;

Listening to multiple events

The addListener, removeListener, and subscribe methods all accept either a single event name or an array of event names, allowing you to register one listener for multiple events at once:

type AddEvent = {
a: number;
b: number;
};
type RemoveEvent = {
id: number;
};
type EventMap = {
add: AddEvent;
remove: RemoveEvent;
};

const eventBus = new EventBus<EventMap>({
adapter: new MemoryEventBusAdapter(),
});

// The same listener handles both "add" and "remove" events
await eventBus.addListener(["add", "remove"], (event) => {
console.log("EVENT:", event);
// event.type will be "add" or "remove" depending on which was dispatched
});

await eventBus.dispatch("add", { a: 1, b: 2 });
await eventBus.dispatch("remove", { id: 42 });

You can also use subscribe to get a single cleanup function that unsubscribes from all listed events at once:

const unsubscribe = await eventBus.subscribe(["add", "remove"], (event) => {
console.log("EVENT:", event);
});

await eventBus.dispatch("add", { a: 1, b: 2 });
await eventBus.dispatch("remove", { id: 42 });

// Unsubscribes from both "add" and "remove" in one call
await unsubscribe();

Separating dispatching and listening

The library includes two additional contracts:

This separation makes it easy to visually distinguish the two contracts, making it immediately obvious that they serve different purposes.

import type {
IEventBus,
IEventListenable,
IEventDispatcher,
} from "eridu-tech/event-bus/contracts";
import { MemoryEventBusAdapter } from "eridu-tech/event-bus/memory-event-bus-adapter";
import { EventBus } from "eridu-tech/event-bus";

type AddEvent = {
a: number;
b: number;
};
type EventMap = {
add: AddEvent;
};

async function listenerFunc(
eventListenable: IEventListenable<EventMap>,
): Promise<void> {
// You cannot access the dispatch method
// You will get typescript error if you try

await eventListenable.addListener("add", (event) => {
console.log("EVENT:", event);
});
}

async function dispatchingFunc(
eventDispatcher: IEventDispatcher<EventMap>,
): Promise<void> {
// You cannot access the listener methods
// You will get typescript error if you try

await eventDispatcher.dispatch("add", {
a: 20,
b: 5,
});
}

const eventBus: IEventBus<any> = new EventBus({
// You can choose the adapter to use
adapter: new MemoryEventBusAdapter(),
});

await listenerFunc(eventBus);
await dispatchingFunc(eventBus);

Invocable listeners

An event listener is Invocable meaning you can also pass in an object (class instance or object literal) as listener:

info

For further information refer the Invocable docs.

type AddEvent = {
a: number;
b: number;
};
class Listener implements IEventListenerObject<AddEvent> {
private count = 0;

invoke(event: AddEvent): void {
console.log("EVENT:", event);
console.log("COUNT:", count);
this.count++;
}
}

await eventBus.addListener("add", new Listener());
await eventBus.dispatch("add", {
a: 1,
b: 2,
});
await eventBus.dispatch("add", {
a: 3,
b: -1,
});

Further information

For further information refer to eridu-tech/event-bus API docs.