FastEvent
WebSite
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.
Installation
npm install fastevent
yarn add fastevent
pnpm add fastevent
bun add fastevent
Guide
Event Publishing and Subscription
FastEvent provides complete event emission and subscription functionality, with an API design inspired by eventemitter2.
import { FastEvent } from 'fastevent';
const events = new FastEvent();
const results = events.emit('user/login', { id: 1 });
const results = await events.emitAsync('data/process', { items: [...] });
events.on('user/login', (message) => {
console.log('User login:', message.payload);
});
events.once('startup', () => console.log('Application has started'));
events.on('data/update', handler, {
count: 3,
prepend: true,
filter: (msg) => msg.payload.important
});
events.onAny((message) => {
console.log('Event occurred:', message.type);
});
Event Messages
Listener functions receive a Message object that contains the following properties:
events.on('user/login', (message) => {
});
Retained Events
Retain the last event data, so subsequent subscribers can immediately receive the event value upon subscription:
const events = new FastEvent();
events.emit('config/theme', { dark: true }, true);
events.emit('config/theme', { dark: true }, { retain: true });
events.on('config/theme', (message) => {
console.log('Theme:', message.payload);
});
Hierarchical Event Publishing
FastEvent supports hierarchical event publishing and subscription.
- The default event hierarchy delimiter is
/, which can be modified via options.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();
events.on('user/*/login', (message) => {
console.log('Any user type login:', message.payload);
});
events.on('user/**', (message) => {
console.log('All user-related events:', message.payload);
});
events.emit('user/admin/login', { id: 1 });
events.emit('user/admin/profile/update', { name: 'New' });
Removing Listeners
FastEvent provides multiple ways to remove listeners:
const subscriber = events.on('user/login', handler);
subscriber.off();
events.off(listener);
events.off('user/login');
events.off('user/login', listener);
events.off('user/*');
events.offAll();
events.offAll('user');
Event Scopes
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();
const userScope = events.scope('user');
userScope.on('login', handler);
events.on('user/login', handler);
userScope.emit('login', data);
events.emit('user/login', data);
userScope.offAll();
Waiting for Events
Use waitFor to wait for a specific event to occur, with timeout support.
const events = new FastEvent();
async function waitForLogin() {
try {
const userData = await events.waitFor('user/login', 5000);
console.log('User logged in:', userData);
} catch (error) {
console.log('Login wait timeout');
}
}
waitForLogin();
events.emit('user/login', { id: 1, name: 'Alice' });
Event Hooks
FastEvent provides multiple hook functions for operations at different stages of the event emitter lifecycle.
const otherEvents = new FastEvent();
const events = new FastEvent({
onAddListener: (type, listener, options) => {
console.log('Added new listener:', type);
return false;
if (type.startsWith('@')) {
return otherEvents.on(type, listener, options);
}
},
onRemoveListener: (type, listener) => {
console.log('Removed listener:', type);
},
onClearListeners: () => {
console.log('All listeners cleared');
},
onListenerError: (error, listener, message, args) => {
console.error(`Error in listener for event ${message.type}:`, error);
},
onBeforeExecuteListener: (message, args) => {
console.log('Before executing event listener');
return false;
if (type.startsWith('@')) {
return otherEvents.emit(message.type);
}
},
onAfterExecuteListener: (message, returns, listeners) => {
console.log('After executing event listener');
},
});
Executors
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 () => {
});
events.on('task/start', async () => {
});
await events.emitAsync('task/start');
Built-in Support:
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
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();
events.on(
'data/update',
(data) => {
console.log('Processing data:', data);
},
{
pipes: [queue({ size: 10 })],
},
);
Built-in Support:
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 |
Forwarding Publishing and Subscription
FastEvent can elegantly forward publishing and subscription to another FastEvent instance.
const otherEmitter = new FastEvent();
const emitter = new FastEvent({
onAddListener: (type, listener, options) => {
if (type.startsWith('@/')) {
return otherEmitter.on(type.substring(2), listener, options);
}
},
onBeforeExecuteListener: (message, args) => {
if (message.type.startsWith('@/')) {
message.type = message.type.substring(2);
return otherEmitter.emit(message, args);
}
},
});
const events: any[] = [];
otherEmitter.on('data', ({ payload }) => {
events.push(payload);
});
emitter.on('@/data', ({ payload }) => {
expect(payload).toBe(1);
events.push(payload);
});
const subscriber = emitter.emit('@/data', 1);
subscriber.off();
Metadata (Meta)
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);
});
const userScope = events.scope('user', {
meta: { domain: 'user' },
});
userScope.emit(
'login',
{ userId: '123' },
{
meta: { timestamp: Date.now() },
},
);
userScope.on('login', (message) => {
console.log('Metadata:', message.meta);
});
Event Type Definitions
FastEvent has complete TypeScript type support.
interface ComplexEvents {
'data/number': number;
'data/string': string;
'data/object': { value: any };
}
const events = new FastEvent<ComplexEvents>();
events.on('data/number', (message) => {
const sum = message.payload + 1;
});
events.emit('data/number', 42);
events.emit('data/string', 'hello');
events.emit('data/object', { value: true });
Unit Testing
FastEvent has been thoroughly unit tested, with over 280+ cumulative test cases and 99%+ test coverage.
License
MIT
For more detailed documentation, see WebSite