Event Triggering
FastEvent provides a flexible and powerful event triggering mechanism that supports various triggering methods and options.
Event Triggering Methods
The emit
or emitAsync
methods are used for synchronous or asynchronous event triggering.
Basic Triggering Method
The simplest way to trigger an event is using event type
and data payload
as parameters:
const events = new FastEvent();
// Trigger simple event
emitter.emit('user/login', { id: 1, name: 'Alice' });
await emitter.emitAsync('user/login', { id: 1, name: 'Alice' });
// Trigger event and retain event message
emitter.emit(
'user/login',
{ id: 1, name: 'Alice' }, // Event data
true, // Whether to retain
);
await emitter.emitAsync(
'user/login',
{ id: 1, name: 'Alice' }, // Event data
true, // Whether to retain
);
Message Object Form
You can also trigger events using a message object, which provides more flexibility:
emitter.emit({
type: 'user/login', // Event type
payload: { id: 1, name: 'Alice' }, // Event data payload
meta: { timestamp: Date.now() }, // Optional metadata
});
await emitter.emitAsync({
type: 'user/login', // Event type
payload: { id: 1, name: 'Alice' }, // Event data payload
meta: { timestamp: Date.now() }, // Optional metadata
});
Guidelines
Event Trigger Results
emit
and emitAsync
execute registered listener functions and return their results when triggering events.
emitter.on('event', () => 1);
emitter.on('event', () => 2);
emitter.on('event', () => 3);
const result = emitter.emit('event'); // [1, 2, 3]
const result = await emitter.emitAsync('event'); // [1, 2, 3]
Call Count Limit
When triggering events, you can limit the maximum number of times a listener is called:
// Remove listener after maximum of 3 calls
emitter.on('event', handler, { count: 3 });
// Equivalent once syntax sugar
emitter.on('event', handler, { count: 1 });
// Same as
emitter.once('event', handler);
Prepend Insertion
By default, when registering listeners using on/once
, the listener is added to the end of the queue. Enabling prepend=true
will add the listener to the beginning of the queue instead of the end, affecting the execution order of listeners.
emitter.on('event', handler1);
emitter.on('event', handler2, { prepend: true });
// Execution order: handler2 -> handler1
Retained Events
Setting retain=true
retains events for subsequent subscribers:
// Trigger and retain event
emitter.emit('system/status', { online: true }, true);
// Equivalent to: emitter.emit('system/status', { online: true }, { retain: true })
// Subsequent subscribers will immediately receive the retained event
emitter.on('system/status', (message) => {
console.log('Current status:', message.payload.online);
});
Return Results
emit
and emitAsync
execute registered listener functions and return their results when triggering events.
import { FastEvent } from 'fastevent';
const emitter = new FastEvent();
emitter.on('event', async (message) => {
await delay(100);
return 1;
});
emitter.on('event', async (message) => {
await delay(300);
return 2;
});
emitter.on('event', async (message) => {
await delay(500);
return 3;
});
// Trigger async event and wait for all listeners to complete
// @noErrors
const results = await emitter.emitAsync('event');
// ^^^^^^^
// results === [1, 2, 3]
// @noErrors
const results = emitter.emit('event');
// ^^^^^^^
// results === [1, 2, 3]
Error Handling
When async listener functions throw errors, the emit/emitAsync
return results will include Error
objects.
const emitter = new FastEvent();
emitter.on('event', async (message) => {
await delay(100);
return 1;
});
emitter.on('event', async (message) => {
throw new Error('custom error');
});
emitter.on('event', async (message) => {
await delay(500);
return 3;
});
// Trigger async event and wait for all listeners to complete
const results = await emitter.emitAsync('event');
// results === [1,
// Error('custom error'),
// 3]
Triggering with Metadata
Additional metadata can be specified when triggering events to pass to listeners.
import { FastEvent } from 'fastevent';
const emitter = new FastEvent();
emitter.emit('order/create', { orderId: '123', total: 99.99 }, false, {
timestamp: Date.now(),
source: 'web',
userId: 'user_123',
});
emitter.on('order/create', (message) => {
message.meta; // { timestamp: number; source: string; userId: string; }
});
message.meta
is an object containing additional metadata.- Types of metadata specified during
emit
cannot be automatically inferred. - For more information about metadata, see Metadata.
Type-Safe Event Triggering
When using TypeScript
, FastEvent
provides complete type checking:
import { FastEvent } from 'fastevent';
interface MyEvents {
'user/login': { id: number; name: string };
'user/logout': { id: number };
'system/error': { code: string; message: string };
}
const emitter = new FastEvent<MyEvents>();
emitter.onAny<number>((message) => {});
// ✅ Correct: Data type matches
emitter.emit('user/login', { id: 1, name: 'Alice' });
// ✅ Correct: Message object
emitter.emit({
type: 'user/login',
payload: { id: 1, name: 'Alice' },
});
// ✅ Correct: Supports triggering undefined event types
emitter.emit({
type: 'xxxxx',
payload: { id: 1, name: 'Alice' },
});
// ✅ Correct: Supports triggering undefined event types
emitter.emit('xxxx', 1);
// ❌ Error: Declared event type payload doesn't match
events.emit('user/login', { id: '1', name: 'Alice' }); // TypeScript error
events.emit('user/login', 1); // TypeScript error
events.emit('order', '1');
// ❌ Error: id type doesn't match
events.emit({
type: 'user/login',
payload: { id: '1', name: 'Alice' },
});
The first generic parameter of FastEvent
is used to declare event types:
interface MyEvents {
[EventTypeName]: <EventMessagePayloadType>
}
const events = new FastEvent<MyEvents>();
Note
In the example above, we declared the MyEvents
interface, why is the following type correct?
emitter.emit({
type: 'xxxxx',
payload: { id: 1, name: 'Alice' },
});
Because FastEvent.emit
provides multiple different function call signatures, when type
is string
, payload
is inferred as any
, so the emit
function call is correct.
API
emit
emit
supports multiple calling methods, with the following function signatures:
emit<R = any, T extends Types = Types>(type: T, payload?: Events[T], retain?: boolean): R[]
emit<R = any, T extends string = string>(type: T, payload?: T extends Types ? Events[Types] : any, retain?: boolean): R[]
emit<R = any, T extends string = string>(message: FastEventEmitMessage<{ [K in T]: K extends Types ? Events[K] : any }, Meta>, retain?: boolean): R[]
emit<R = any>(message: FastEventEmitMessage<Events, Meta>, retain?: boolean): R[]
//----
emit<R = any, T extends Types = Types>(type: T, payload?: Events[T], options?: FastEventListenerArgs<Meta>): R[]
emit<R = any, T extends string = string>(type: T, payload?: T extends Types ? Events[Types] : any, options?: FastEventListenerArgs<Meta>): R[]
emit<R = any, T extends string = string>(message: FastEventEmitMessage<{ [K in T]: K extends Types ? Events[K] : any }, Meta>, options?: FastEventListenerArgs<Meta>): R[]
emit<R = any>(message: FastEventEmitMessage<Events, Meta>, options?: FastEventListenerArgs<Meta>): R[]
The rich call signatures of emit
above can be broadly categorized into the following situations:
- Trigger events by specifying
type
andpayload
emitter.emit('click', 100);
emitter.emit('click', 100, true); // Retain message
emitter.emit('click', 100, { retain: true }); // Retain message
emitter.emit('click', 100, { ... }); // Carry additional trigger parameters
- Trigger events by specifying
message
emitter.emit({ type: 'click', payload: 100 });
// Retain message
emitter.emit({ type: 'click', payload: 100 }, true);
// Retain message
emitter.emit({ type: 'click', payload: 100 }, { retain: true });
// Carry additional trigger parameters
emitter.emit({ type: 'click', payload: 100 }, { retain: true, .... });
emitAsync
emitAsync
is the asynchronous version of emit
, implemented as follows:
public async emitAsync<R = any>(): Promise<[R | Error][]> {
const results = await Promise.allSettled(this.emit.apply(this, arguments as any))
return results.map((result) => {
if (result.status === 'fulfilled') {
return result.value
} else {
return result.reason
}
})
}
It is used in the same way as emit
, with the difference being that it internally calls Promise.allSettled
and returns Promise<[R | Error][]>
.
Trigger Parameters
The emit/emitAsync
methods allow passing additional parameters that control event triggering behavior.
export type FastEventListenerArgs<M = Record<string, any>> = {
retain?: boolean;
meta?: Record<string, any> & Partial<M>;
abortSignal?: AbortSignal; // Used to pass to listener functions
executor?: FastListenerExecutorArgs;
};
Parameter Name | Type | Description |
---|---|---|
retain | boolean | Whether to retain the message. If true, the last message is retained. See Retained Messages |
meta | Record<string, any> | Event metadata, passed to listeners |
abortSignal | AbortSignal | Used to pass to listener functions. See Aborting Listeners |
executor | FastListenerExecutorArgs | Used to control listener execution behavior. See Executor |