Skip to content

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:

typescript
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:

typescript
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.

typescript
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:

typescript
// 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.

typescript
emitter.on('event', handler1);
emitter.on('event', handler2, { prepend: true });
// Execution order: handler2 -> handler1

Retained Events

Setting retain=true retains events for subsequent subscribers:

typescript
// 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.

typescript
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.

typescript
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.

typescript
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:

typescript
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:

typescript
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?

ts
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:

ts
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 and payload
typescript
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
typescript
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:

ts
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.

typescript
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