Quick Start
This section will help you quickly get started with FastEvent, understanding its core features and basic usage.
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();
// 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);
});
Event Messages
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
// }
});
Retained Events
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 }
});
Hierarchical Event Publishing
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 called
Removing Listeners
FastEvent
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');
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();
// 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')
Waiting for Events
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' });
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({
// 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
},
});
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 () => {
/* 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
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 |
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) => {
// 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 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 (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); // 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);
});
Event Type Definitions
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 });
Unit Testing
FastEvent
has been thoroughly unit tested, with over 280+
cumulative test cases and 99%+
test coverage.