FastEvent is a well-designed, powerful, type-safe, and thoroughly tested event emitter that provides robust event subscription and publishing mechanisms, suitable for both nodejs/browser environments.
npm install fastevent
yarn add fastevent
pnpm add fastevent
bun add fasteventFastEvent provides complete event emission and subscription functionality, with an API design inspired by eventemitter2.
import { FastEvent } from 'fastevent';
const events = new FastEvent();
// Basic event publishing
const results = events.emit('user/login', { id: 1 });
// Asynchronous event emission
const results = await events.emitAsync('data/process', { items: [...] });
// Event subscription
events.on('user/login', (message) => {
    console.log('User login:', message.payload);
});
// One-time listener
events.once('startup', () => console.log('Application has started'));
// Listener with options
events.on('data/update', handler, {
    count: 3,       // Maximum trigger count
    prepend: true,  // Add to the beginning of the queue
    filter: (msg) => msg.payload.important // Only process important updates
});
// Global listener
events.onAny((message) => {
    console.log('Event occurred:', message.type);
});Listener functions receive a Message object that contains the following properties:
events.on('user/login', (message) => {
    // {
    //     type: 'user/login', // Event name
    //     payload: { id: 1 }, // Event data
    //     meta: {...}         // Event metadata
    // }
});Retain the last event data, so subsequent subscribers can immediately receive the event value upon subscription:
const events = new FastEvent();
// Publish and retain event
events.emit('config/theme', { dark: true }, true);
// Equivalent to
events.emit('config/theme', { dark: true }, { retain: true });
// Subsequent subscribers immediately receive the retained value
events.on('config/theme', (message) => {
    console.log('Theme:', message.payload); // Immediately outputs: { dark: true }
});FastEvent supports hierarchical event publishing and subscription.
- The default event hierarchy delimiter is 
/, which can be modified viaoptions.delimiter - Two types of wildcards are supported when subscribing to events: 
*matches a single path level,**matches multiple path levels (only used at the end of event names) 
const events = new FastEvent();
// Match user/*/login
events.on('user/*/login', (message) => {
    console.log('Any user type login:', message.payload);
});
// Match all events under user
events.on('user/**', (message) => {
    console.log('All user-related events:', message.payload);
});
// Trigger events
events.emit('user/admin/login', { id: 1 }); // Both handlers will be called
events.emit('user/admin/profile/update', { name: 'New' }); // Only the ** handler will be calledFastEvent provides multiple ways to remove listeners:
// Return a subscriber object to remove the listener, recommended approach
const subscriber = events.on('user/login', handler);
subscriber.off();
// Remove a specific listener
events.off(listener);
// Remove all listeners for a specific event
events.off('user/login');
// Remove a specific listener for a specific event
events.off('user/login', listener);
// Remove listeners using wildcard patterns
events.off('user/*');
// Remove all listeners
events.offAll();
// Remove all listeners under a specific prefix
events.offAll('user');Scopes allow you to handle events within a specific namespace.
Note that scopes share the same listener table with the parent event emitter:
const events = new FastEvent();
// Create a user-related scope
const userScope = events.scope('user');
// The following two approaches are equivalent:
userScope.on('login', handler);
events.on('user/login', handler);
// The following two approaches are also equivalent:
userScope.emit('login', data);
events.emit('user/login', data);
// Clear all listeners in the scope
userScope.offAll(); // Equivalent to events.offAll('user')Use waitFor to wait for a specific event to occur, with timeout support.
const events = new FastEvent();
async function waitForLogin() {
    try {
        // Wait for login event with a 5-second timeout
        const userData = await events.waitFor('user/login', 5000);
        console.log('User logged in:', userData);
    } catch (error) {
        console.log('Login wait timeout');
    }
}
waitForLogin();
// Later trigger the login event
events.emit('user/login', { id: 1, name: 'Alice' });FastEvent provides multiple hook functions for operations at different stages of the event emitter lifecycle.
const otherEvents = new FastEvent();
const events = new FastEvent({
    // Called when a new listener is added
    onAddListener: (type, listener, options) => {
        console.log('Added new listener:', type);
        // Return false to prevent the listener from being added
        return false;
        // Can directly return a FastEventSubscriber
        // For example: transfer events starting with `@` to another FastEvent
        if (type.startsWith('@')) {
            return otherEvents.on(type, listener, options);
        }
    },
    // Called when a listener is removed
    onRemoveListener: (type, listener) => {
        console.log('Removed listener:', type);
    },
    // Called when listeners are cleared
    onClearListeners: () => {
        console.log('All listeners cleared');
    },
    // Called when a listener throws an error
    onListenerError: (error, listener, message, args) => {
        console.error(`Error in listener for event ${message.type}:`, error);
    },
    // Called before a listener executes
    onBeforeExecuteListener: (message, args) => {
        console.log('Before executing event listener');
        // Return false to prevent listener execution
        return false;
        // Forward events to another FastEvent
        // For example: forward events starting with `@` to another FastEvent
        if (type.startsWith('@')) {
            return otherEvents.emit(message.type);
        }
    },
    // Called after a listener executes
    onAfterExecuteListener: (message, returns, listeners) => {
        console.log('After executing event listener');
        // Can intercept and modify return values here
    },
});By default, all listeners are executed in parallel when an event is triggered.
FastEvent provides powerful listener execution mechanisms that allow developers to control how listeners are executed.
import { race } from 'fastevent/executors';
const events = new FastEvent({
    executor: race(),
});
events.on('task/start', async () => {
    /* Time-consuming operation 1 */
});
events.on('task/start', async () => {
    /* Time-consuming operation 2 */
});
// The two listeners will execute in parallel, returning the fastest result
await events.emitAsync('task/start');Built-in Support:
| Executor | Description | 
|---|---|
parallel | 
Default, concurrent execution | 
race | 
Parallel executor, uses Promise.race for parallel execution | 
balance | 
Evenly distributed executor | 
first | 
Execute only the first listener | 
last | 
Execute only the last listener | 
random | 
Randomly select a listener | 
series | 
Serial executor, execute listeners in sequence and return the last result | 
waterfall | 
Execute listeners in sequence and return the last result, abort on error | 
(listeners,message,args,execute)=>any[] | 
Custom executor | 
Listener pipes are used to wrap listener functions during event subscription to implement various common advanced features.
import { queue } from 'fastevent/pipes';
const events = new FastEvent();
// default queue size is 10
events.on(
    'data/update',
    (data) => {
        console.log('Processing data:', data);
    },
    {
        pipes: [queue({ size: 10 })],
    },
);Built-in Support:
| Pipe | Description | 
|---|---|
queue | 
Queue listener, process messages in queue, supports priority and timeout control | 
throttle | 
Throttle listener | 
debounce | 
Debounce listener | 
timeout | 
Timeout listener | 
retry | 
Retry listener, for controlling retries after listener execution failure | 
memorize | 
Cache listener, cache listener execution results | 
FastEvent can elegantly forward publishing and subscription to another FastEvent instance.
import { expandable } from 'fastevent';
const otherEmitter = new FastEvent();
const emitter = new FastEvent({
    onAddListener: (type, listener, options) => {
        // Subscription forwarding rule: when event name starts with `@/`, forward subscription to another `FastEvent` instance
        if (type.startsWith('@/')) {
            return otherEmitter.on(type.substring(2), listener, options);
        }
    },
    onBeforeExecuteListener: (message, args) => {
        // Event forwarding rule: when event name starts with `@/`, publish to another `FastEvent` instance
        if (message.type.startsWith('@/')) {
            message.type = message.type.substring(2);
            return expandable(otherEmitter.emit(message, args));
        }
    },
});
const events: any[] = [];
otherEmitter.on('data', ({ payload }) => {
    events.push(payload);
});
// Subscribe to otherEmitter's data event
emitter.on('@/data', ({ payload }) => {
    expect(payload).toBe(1);
    events.push(payload);
});
// Publish data event to otherEmitter
const subscriber = emitter.emit('@/data', 1);
subscriber.off();Metadata is a mechanism for providing additional contextual information for events.
You can set metadata at different levels: global, scope level, or event-specific level.
const events = new FastEvent({
    meta: {
        version: '1.0',
        environment: 'production',
    },
});
events.on('user/login', (message) => {
    console.log('Event data:', message.payload);
    console.log('Metadata:', message.meta); // Includes type, version, and environment
});
// Using scope-level metadata
const userScope = events.scope('user', {
    meta: { domain: 'user' },
});
// Add specific metadata when publishing events
userScope.emit(
    'login',
    { userId: '123' },
    {
        meta: { timestamp: Date.now() }, // Event-specific metadata
    },
);
// Listeners receive merged metadata
userScope.on('login', (message) => {
    console.log('Metadata:', message.meta);
});FastEvent has complete TypeScript type support.
// Define events with different payload types
interface ComplexEvents {
    'data/number': number;
    'data/string': string;
    'data/object': { value: any };
}
const events = new FastEvent<ComplexEvents>();
// TypeScript ensures type safety for each event
events.on('data/number', (message) => {
    const sum = message.payload + 1; // payload type is number
});
// All event emissions are type-checked
events.emit('data/number', 42);
events.emit('data/string', 'hello');
events.emit('data/object', { value: true });By default, the FastEvent listener receives messages in the format of FastEventMessage, which includes three fields: type, payload, and optional meta.
However, FastEvent also provides customization capabilities, allowing each event to receive different messages with corresponding type prompts.
import { FastEvent, FastEventOptions, NotPayload } from 'fastevent';
// {<event type>:<payload>}
type CustomEvents = {
    // NotPayload is used to indicate that it is not a payload, but a complete message body
    click: NotPayload<{ x: number; y: number }>;
    'div/mousemove': boolean;
    'div/scroll': number;
    'div/focus': string;
};
const emitter = new FastEvent<CustomEvents>({
    //Convert standard FastEventMessage to the format you need
    transform: (message: FastEventMessage) => {
        if (['div/click', 'div/mousemove'].includes(message.type)) {
            return message.payload;
        }
        return message;
    },
});
emitter.on('click', (message) => {
    // typeof message === { x: number; y: number }  ✅
});NotPayload is only used to identify some events, and Transformed can also be used to message all events.
import { FastEvent, FastEventOptions, TransformedEvents } from 'fastevent';
// {<event type>:<message>}
type CustomEvents = TransformedEvents<{
    click: { x: number; y: number };
    'div/mousemove': boolean;
    'div/scroll': number;
    'div/focus': string;
}>;transformis used to convert standard FastEventMessage into the format you needNotPayloadandTransformonEventsare used to declare types, in order to provide type declarations for listeners whenon/once.
FastEvent has been thoroughly unit tested, with over 320+ cumulative test cases and 99%+ test coverage.
MIT
For more detailed documentation, see WebSite