Now.js Framework Documentation
EventManager - Custom Event System
EventManager - Custom Event System
Documentation for EventManager, a Custom Event System for managing Application-level Events
📋 Table of Contents
- Overview
- Installation and Import
- Basic Usage
- Core API
- Patterns and Wildcards
- Event Groups
- Priority System
- Middleware
- Async Events
- Context Management
- Cleanup and Memory Management
- Integration with Now.js
- Usage Examples
- API Reference
- Best Practices
Overview
EventManager is a Custom Event System designed for managing Application-level Events (not DOM Events) with advanced capabilities beyond standard EventEmitter
Key Features
- ✅ Event Bus Pattern: Centralized Publish/Subscribe system
- ✅ Pattern Matching: Wildcards and Regular Expression support
- ✅ Event Hierarchy: Namespaced Events (dot notation)
- ✅ Event Groups: Group events for collective management
- ✅ Priority System: Control listener execution order
- ✅ Middleware: Intercept and modify events
- ✅ Async Support: Async/Await and Promises
- ✅ Context Control: Event propagation control
- ✅ Auto Cleanup: Automatic memory management
- ✅ Debug Mode: Event history tracking and debug info
Differences from EventSystemManager
| Feature | EventManager | EventSystemManager |
|---|---|---|
| Purpose | Application Events | DOM Events |
| Core API | on(), emit(), off() |
addEventListener() |
| Event Types | Custom events | DOM events (click, submit, etc.) |
| Target | Application logic | DOM elements |
| Patterns | ✅ Wildcards support | ❌ Not supported |
| Middleware | ✅ Supported | ❌ Not supported |
| Groups | ✅ Supported | ❌ Not supported |
| Priority | ✅ Supported | ✅ Supported |
When to Use EventManager
✅ Use EventManager when:
- You need an Event Bus for your application
- You need communication between Components
- You need to decouple Modules
- You need Pattern Matching (wildcards)
- You need Event Groups
- You need Middleware
❌ Don't use EventManager when:
- You need to handle DOM Events (use EventSystemManager)
- You need Event Delegation
- You need Native DOM Event Features
Installation and Import
EventManager is loaded with the Now.js Framework and is ready to use immediately via the window object:
// No import needed - ready to use immediately
console.log(window.EventManager); // EventManager object
// Or access via Now.js
const eventManager = Now.getManager('event');Dependencies
EventManager requires these dependencies:
- ErrorManager - For error handling
- Utils - For UUID generation (optional)
Initialization
// Initialize with default config
await EventManager.init();
// Or initialize with custom config
await EventManager.init({
asyncTimeout: 10000,
maxListeners: 1000,
traceEvents: true,
cleanup: {
enabled: true,
interval: 60000,
maxAge: 1800000
}
});Basic Usage
1. Register Listener (on)
// Basic
EventManager.on('user:login', (context) => {
console.log('User logged in:', context.data);
});
// With options
EventManager.on('user:login', (context) => {
console.log('User logged in:', context.data.userId);
}, {
priority: 10,
once: false
});2. Emit Event (emit)
// Emit event with data
await EventManager.emit('user:login', {
userId: 123,
username: 'john_doe',
timestamp: Date.now()
});
// Get results from listeners
const results = await EventManager.emit('calculate:sum', {
numbers: [1, 2, 3, 4, 5]
});
console.log('Results:', results); // [15]3. Remove Listener (off)
// Remove all listeners for event
EventManager.off('user:login');
// Remove specific listener
const unsubscribe = EventManager.on('user:login', handler);
unsubscribe(); // Remove this listener
// Or use listener ID
EventManager.off('user:login', listenerId);4. Listen Once (once)
// Execute once then remove
EventManager.once('app:initialized', (context) => {
console.log('App initialized!');
});
// Or use once option
EventManager.on('popup:shown', handler, {once: true});Core API
on(event, callback, options?)
Register a listener for an event
const unsubscribe = EventManager.on(eventName, callback, options);Parameters:
event(string) - Event name or patterncallback(function) - Function to call, receives context objectoptions(object) - optionalpriority(number) - Priority (higher = first) default: 0once(boolean) - Execute once default: falseasync(boolean) - Async handler support default: falsegroup(string) - Group nametimeout(number) - Async timeout (ms) default: 5000maxRetries(number) - Retry attempts on error default: 1onError(function) - Error handler
Returns: Function to unsubscribe
Examples:
// Basic
EventManager.on('message:received', (context) => {
console.log('Message:', context.data.text);
});
// With priority
EventManager.on('data:processing', handleData, {
priority: 100 // Execute before other listeners
});
// With group
EventManager.on('notification:*', handleNotification, {
group: 'notifications'
});
// Async handler
EventManager.on('user:register', async (context) => {
await sendWelcomeEmail(context.data.email);
return {success: true};
}, {
async: true,
timeout: 10000
});emit(event, data?)
Emit an event with data
const results = await EventManager.emit(eventName, data);Parameters:
event(string) - Event namedata(object) - Data to send optional
Returns: Promise - Array of results from listeners
Examples:
// Emit event
await EventManager.emit('cart:item-added', {
productId: 123,
quantity: 2,
price: 99.99
});
// Get results
const results = await EventManager.emit('validate:form', {
formData: {...}
});
const isValid = results.every(r => r === true);
// Emit without data
await EventManager.emit('app:ready');off(event, identifier?)
Remove listeners
const removed = EventManager.off(eventName, identifier);Parameters:
event(string) - Event name or patternidentifier(string|function) - optional- If not specified: remove all
- string: remove by ID
- function: remove by callback reference
Returns: boolean - true if removed successfully
Examples:
// Remove all
EventManager.off('user:login');
// Remove specific listener
const handler = (ctx) => console.log(ctx.data);
EventManager.on('event', handler);
EventManager.off('event', handler);
// Use unsubscribe function
const unsubscribe = EventManager.on('event', handler);
unsubscribe();once(event, callback, options?)
Register a one-time listener
const unsubscribe = EventManager.once(eventName, callback, options);Same as on() but automatically sets once: true
Examples:
// Execute once
EventManager.once('app:loaded', (context) => {
console.log('App loaded at:', context.timestamp);
});
// With priority
EventManager.once('init:complete', handler, {
priority: 100
});Patterns and Wildcards
EventManager supports pattern matching for event names
Wildcard Operators
| Operator | Meaning | Example | Matches |
|---|---|---|---|
* |
Match any characters | user:* |
user:login, user:logout, user:register |
? |
Match single character | user:log?n |
user:login |
+ |
Match preceding character 1+ times | (use with regex) | - |
| |
OR operator | (use with regex) | - |
Pattern Examples
// Match all events starting with user:
EventManager.on('user:*', (context) => {
console.log('User event:', context.event);
});
// Match all notification types
EventManager.on('notification:*', (context) => {
showNotification(context.data.message);
});
// Match multi-level events
EventManager.on('api:*:success', (context) => {
// Matches api:user:success, api:product:success, etc.
console.log('API success:', context.event);
});
// Use single character wildcard
EventManager.on('page:?', (context) => {
// Matches page:1, page:2, page:a, page:b
});Custom Regex Patterns
// Use regex pattern directly
EventManager.on(/^user:(login|logout|register)$/, (context) => {
console.log('Auth event:', context.event);
});
// Complex pattern
EventManager.on(/^api:\w+:(get|post|put|delete)$/, (context) => {
logApiCall(context);
});Pattern Priority
When an event matches multiple patterns:
- Exact match executes first
- Pattern matches execute by priority
- Listeners within same pattern execute by priority
// Exact match - highest priority
EventManager.on('user:login', handler1, {priority: 10});
// Pattern match - executes after exact match
EventManager.on('user:*', handler2, {priority: 100}); // Even with higher priority
// When emit('user:login')
// Executes: handler1 -> handler2Event Groups
Group events for collective management
Create Group
// Add listeners to group
EventManager.on('notification:info', handler1, {group: 'notifications'});
EventManager.on('notification:warning', handler2, {group: 'notifications'});
EventManager.on('notification:error', handler3, {group: 'notifications'});Emit Group
Emit to all events in group
// Emit to all events in group
const results = await EventManager.emitGroup('notifications', {
message: 'System update completed',
level: 'info'
});Use Cases for Groups
1. Notification System
// Group notification handlers
EventManager.on('notification:info', showInfoNotification, {
group: 'notifications'
});
EventManager.on('notification:success', showSuccessNotification, {
group: 'notifications'
});
EventManager.on('notification:error', showErrorNotification, {
group: 'notifications'
});
// Emit to entire group
await EventManager.emitGroup('notifications', {
message: 'Operation completed'
});2. Analytics Tracking
// Group analytics events
EventManager.on('analytics:pageview', trackPageView, {
group: 'analytics'
});
EventManager.on('analytics:click', trackClick, {
group: 'analytics'
});
EventManager.on('analytics:conversion', trackConversion, {
group: 'analytics'
});
// Track all analytics
await EventManager.emitGroup('analytics', {
session: sessionId,
timestamp: Date.now()
});3. Feature Toggles
// Group feature events
EventManager.on('feature:enabled', enableFeature, {
group: 'features'
});
EventManager.on('feature:disabled', disableFeature, {
group: 'features'
});
// Emit to all features
await EventManager.emitGroup('features', {
feature: 'dark-mode',
enabled: true
});Priority System
Control listener execution order
Priority Values
- Higher number = Higher priority (executes first)
- Lower number = Lower priority (executes later)
- Default = 0
// High priority - executes first
EventManager.on('data:process', validateData, {
priority: 100
});
// Medium priority
EventManager.on('data:process', transformData, {
priority: 50
});
// Low priority - executes later
EventManager.on('data:process', saveData, {
priority: 10
});
// Default priority (0) - executes last
EventManager.on('data:process', logData);
// When emit('data:process')
// Executes in order: validateData -> transformData -> saveData -> logDataUse Cases for Priority
1. Data Processing Pipeline
// Validation must run first
EventManager.on('form:submit', validateForm, {
priority: 100
});
// Transformation after validation
EventManager.on('form:submit', transformData, {
priority: 80
});
// Sanitization
EventManager.on('form:submit', sanitizeData, {
priority: 60
});
// Save data - executes last
EventManager.on('form:submit', saveToDatabase, {
priority: 10
});2. Authentication Flow
// Verify token first
EventManager.on('request:*', verifyToken, {
priority: 1000
});
// Check permissions
EventManager.on('request:*', checkPermissions, {
priority: 900
});
// Rate limiting
EventManager.on('request:*', checkRateLimit, {
priority: 800
});
// Actual work - lower priority
EventManager.on('request:create', createResource, {
priority: 100
});3. Error Handling
// Log error first
EventManager.on('error:*', logError, {
priority: 100
});
// Notify admin
EventManager.on('error:critical', notifyAdmin, {
priority: 90
});
// Show error to user
EventManager.on('error:*', showErrorMessage, {
priority: 50
});Middleware
Intercept and modify events before passing to listeners
Creating Middleware
// Function middleware
EventManager.use((context) => {
console.log('Event:', context.event);
console.log('Data:', context.data);
// return false to stop event
if (context.data.blocked) {
return false;
}
// return true or undefined to continue
return true;
});
// Object middleware with hooks
EventManager.use({
beforeEmit: (context) => {
// Run before emit
console.log('Before emit:', context.event);
// Modify data
context.data.timestamp = Date.now();
return true; // Continue
},
afterEmit: (context) => {
// Run after emit
console.log('After emit:', context.event);
console.log('Results:', context.results);
}
});Middleware Hooks
| Hook | When | Use Case |
|---|---|---|
beforeEmit |
Before emitting event | Validation, logging, data transformation |
afterEmit |
After emit completes | Logging results, cleanup, notifications |
Middleware Examples
1. Logging Middleware
EventManager.use({
beforeEmit: (context) => {
console.log(`[Event] ${context.event}`, {
timestamp: context.timestamp,
data: context.data
});
},
afterEmit: (context) => {
console.log(`[Event Complete] ${context.event}`, {
duration: Date.now() - context.timestamp,
results: context.results.length
});
}
});2. Authentication Middleware
EventManager.use((context) => {
// Check authentication for events requiring auth
if (context.event.startsWith('api:')) {
if (!isAuthenticated()) {
console.error('Unauthorized event:', context.event);
return false; // block event
}
}
return true;
});3. Data Validation Middleware
EventManager.use({
beforeEmit: (context) => {
// Validate data structure
if (context.data && typeof context.data !== 'object') {
console.error('Invalid data type for event:', context.event);
return false;
}
// Add default values
context.data.timestamp = context.data.timestamp || Date.now();
context.data.source = context.data.source || 'app';
return true;
}
});4. Rate Limiting Middleware
const eventCounts = new Map();
EventManager.use((context) => {
const key = `${context.event}:${Date.now() / 1000 | 0}`;
const count = eventCounts.get(key) || 0;
if (count >= 100) {
console.warn('Rate limit exceeded for:', context.event);
return false;
}
eventCounts.set(key, count + 1);
return true;
});5. Error Tracking Middleware
EventManager.use({
afterEmit: (context) => {
// Track errors in results
const errors = context.results.filter(r => r instanceof Error);
if (errors.length > 0) {
console.error('Errors in event:', context.event, errors);
// Send to error tracking service
ErrorManager.handle(new Error('Event execution errors'), {
context: context.event,
data: {errors}
});
}
}
});Async Events
Support for asynchronous event handlers and promises
Async Handlers
// Async handler
EventManager.on('user:register', async (context) => {
const {email, password} = context.data;
// Async operations
await createUserAccount(email, password);
await sendWelcomeEmail(email);
await logActivity('user:registered', email);
return {success: true, userId: 123};
}, {
async: true,
timeout: 10000 // 10 seconds timeout
});
// Emit and wait for results
const results = await EventManager.emit('user:register', {
email: 'user@example.com',
password: 'securepass'
});
console.log('Registration results:', results);Promise Handlers
EventManager.on('data:fetch', (context) => {
// Return promise
return fetch(context.data.url)
.then(res => res.json())
.then(data => {
console.log('Data fetched:', data);
return data;
});
}, {
async: true
});
// Emit and wait
const results = await EventManager.emit('data:fetch', {
url: '/api/users'
});Timeout Configuration
// Global timeout (default: 5000ms)
await EventManager.init({
asyncTimeout: 10000
});
// Per-listener timeout
EventManager.on('heavy:operation', heavyHandler, {
async: true,
timeout: 30000 // 30 seconds
});Error Handling in Async
EventManager.on('risky:operation', async (context) => {
try {
const result = await riskyAsyncOperation(context.data);
return result;
} catch (error) {
console.error('Operation failed:', error);
return {error: error.message};
}
}, {
async: true,
onError: (error) => {
// Custom error handler
console.error('Async handler error:', error);
notifyAdmin(error);
}
});Parallel Async Execution
// Multiple async handlers execute in parallel
EventManager.on('data:sync', async (context) => {
await syncToDatabase(context.data);
return 'db-synced';
}, {async: true});
EventManager.on('data:sync', async (context) => {
await syncToCache(context.data);
return 'cache-synced';
}, {async: true});
EventManager.on('data:sync', async (context) => {
await syncToSearch(context.data);
return 'search-synced';
}, {async: true});
// Wait for all to complete
const results = await EventManager.emit('data:sync', {
data: {...}
});
console.log('Sync results:', results);
// ['db-synced', 'cache-synced', 'search-synced']Context Management
Event context object passed to listeners
Context Structure
{
event: string, // Event name
data: object, // Event data
timestamp: number, // Emit timestamp
preventDefault: boolean, // Flag to prevent default behavior
stopPropagation: boolean, // Flag to stop propagation
stopImmediatePropagation: boolean, // Stop immediately
results: array, // Results from listeners
source: any, // Event source
path: array, // Event hierarchy path
currentPath: string // Current event path
}Control Propagation
EventManager.on('form:submit', (context) => {
// Validate data
if (!validateForm(context.data)) {
// Stop propagation to other listeners
context.stopPropagation = true;
return {valid: false};
}
return {valid: true};
}, {priority: 100});
EventManager.on('form:submit', (context) => {
// Won't execute if validation fails
saveForm(context.data);
});Prevent Default Behavior
EventManager.on('link:click', (context) => {
// Prevent default behavior
context.preventDefault = true;
// Custom behavior
handleCustomNavigation(context.data.url);
});Stop Immediate Propagation
EventManager.on('critical:error', (context) => {
// Stop other listeners immediately
context.stopImmediatePropagation = true;
// Handle critical error
handleCriticalError(context.data);
}, {priority: 1000});
// Other listeners won't execute
EventManager.on('critical:error', (context) => {
// Won't be called
logError(context.data);
});Access Event Hierarchy
EventManager.on('api:user:created', (context) => {
console.log('Event:', context.event);
// 'api:user:created'
console.log('Path:', context.path);
// ['api:user:created', 'api:user', 'api']
// Listeners called according to hierarchy
});
// All these listeners will be called:
EventManager.on('api:user:created', handler1);
EventManager.on('api:user', handler2);
EventManager.on('api', handler3);Return Values
EventManager.on('calculate:sum', (context) => {
const sum = context.data.numbers.reduce((a, b) => a + b, 0);
return sum; // Return value stored in context.results
});
EventManager.on('calculate:sum', (context) => {
const avg = context.data.numbers.reduce((a, b) => a + b, 0) / context.data.numbers.length;
return avg;
});
// Emit and get results
const results = await EventManager.emit('calculate:sum', {
numbers: [1, 2, 3, 4, 5]
});
console.log('Results:', results); // [15, 3]Cleanup and Memory Management
EventManager has automatic cleanup system for memory management
Auto Cleanup
// Enable cleanup (default: enabled)
await EventManager.init({
cleanup: {
enabled: true,
interval: 60000, // Check every 60 seconds
maxAge: 1800000, // Remove listeners older than 30 minutes
batchSize: 100 // Process 100 items at a time
}
});Manual Cleanup
// Remove unused listeners
EventManager.cleanup();
// Clear all events
EventManager.clear();
// Destroy and cleanup everything
EventManager.destroy();Cleanup Stale Listeners
// Remove old listeners for specific event
EventManager.cleanupStaleListeners('old:event');Best Practices for Memory Management
1. Use Once for One-time Events
// ❌ Bad: listener not removed
EventManager.on('app:initialized', handler);
// ✅ Good: auto-removed after execution
EventManager.once('app:initialized', handler);2. Unsubscribe When Done
// ✅ Good: keep unsubscribe function
const unsubscribe = EventManager.on('data:updated', handler);
// When component destroys
componentWillUnmount() {
unsubscribe();
}3. Use Groups for Batch Cleanup
// Add to group
EventManager.on('temp:event1', handler1, {group: 'temporary'});
EventManager.on('temp:event2', handler2, {group: 'temporary'});
EventManager.on('temp:event3', handler3, {group: 'temporary'});
// Cleanup entire group
// (No direct API but can off each event)
EventManager.off('temp:event1');
EventManager.off('temp:event2');
EventManager.off('temp:event3');4. Monitor Listener Count
// Check listener count
const info = EventManager.getEventInfo('user:login');
console.log('Listener count:', info.listenerCount);
// Warn if too many
if (info.listenerCount > 50) {
console.warn('Too many listeners for event:', 'user:login');
}Integration with Now.js
EventManager is integrated with Now.js Framework
Auto-Registration
// EventManager auto-registered on load
console.log(Now.managers.has('event')); // trueAccess via Now.js
// Method 1: Now.emit() wrapper
Now.emit('user:login', {userId: 123});
// Note: Now.emit() automatically adds timestamp
// Method 2: Via Manager system
const eventManager = Now.getManager('event');
eventManager.emit('user:login', {userId: 123});
// Method 3: Direct access
EventManager.emit('user:login', {userId: 123});Use with Now.js Components
// In component
const MyComponent = {
async init() {
// Register listeners
this.unsubscribe = EventManager.on('data:updated', (context) => {
this.handleDataUpdate(context.data);
});
},
handleDataUpdate(data) {
console.log('Data updated:', data);
this.render();
},
async destroy() {
// Cleanup on destroy
if (this.unsubscribe) {
this.unsubscribe();
}
}
};
ComponentManager.define('my-component', MyComponent);Integration Events
Now.js emits these events via EventManager:
// App lifecycle events
EventManager.on('app:mounted', (context) => {
console.log('App mounted:', context.data.app);
});
EventManager.on('app:ready', (context) => {
console.log('App ready');
});
// Error events
EventManager.on('error', (context) => {
console.error('Error:', context.data.error);
});
// Performance events
EventManager.on('performance:recorded', (context) => {
console.log('Performance metric:', context.data);
});Usage Examples
1. User Authentication Flow
// Listen for login success
EventManager.on('auth:login:success', async (context) => {
const {user, token} = context.data;
// Save token
localStorage.setItem('token', token);
// Load user data
await loadUserData(user.id);
// Redirect
window.location.href = '/dashboard';
}, {
priority: 100
});
// Analytics tracking
EventManager.on('auth:*', (context) => {
analytics.track(context.event, context.data);
}, {
priority: 10
});
// Emit login event
await EventManager.emit('auth:login:success', {
user: {id: 123, name: 'John'},
token: 'abc123'
});2. Form Validation Pipeline
// Validation - highest priority
EventManager.on('form:submit', (context) => {
const errors = validateForm(context.data.formData);
if (errors.length > 0) {
context.stopPropagation = true;
return {valid: false, errors};
}
return {valid: true};
}, {
priority: 100
});
// Sanitization
EventManager.on('form:submit', (context) => {
context.data.formData = sanitizeFormData(context.data.formData);
}, {
priority: 80
});
// Transform
EventManager.on('form:submit', (context) => {
context.data.formData = transformData(context.data.formData);
}, {
priority: 60
});
// Save - lowest priority
EventManager.on('form:submit', async (context) => {
await saveToDatabase(context.data.formData);
return {saved: true};
}, {
priority: 10,
async: true
});
// Submit form
const results = await EventManager.emit('form:submit', {
formData: {...}
});3. Real-time Notification System
// Group notification handlers
EventManager.on('notification:info', (context) => {
showToast(context.data.message, 'info');
}, {group: 'notifications'});
EventManager.on('notification:success', (context) => {
showToast(context.data.message, 'success');
}, {group: 'notifications'});
EventManager.on('notification:warning', (context) => {
showToast(context.data.message, 'warning');
}, {group: 'notifications'});
EventManager.on('notification:error', (context) => {
showToast(context.data.message, 'error');
}, {group: 'notifications'});
// Send notification
await EventManager.emit('notification:success', {
message: 'Data saved successfully!'
});
// Or emit to all notifications
await EventManager.emitGroup('notifications', {
message: 'System updated'
});4. Shopping Cart Events
// Item added
EventManager.on('cart:item-added', async (context) => {
const {product, quantity} = context.data;
// Update UI
updateCartBadge();
// Save
await saveCart();
// Show notification
await EventManager.emit('notification:success', {
message: `Added ${product.name} to cart`
});
});
// Item removed
EventManager.on('cart:item-removed', async (context) => {
updateCartBadge();
await saveCart();
});
// Quantity changed
EventManager.on('cart:quantity-changed', async (context) => {
updateCartTotal();
await saveCart();
});
// Use pattern for analytics
EventManager.on('cart:*', (context) => {
analytics.track(context.event, context.data);
});
// Usage
await EventManager.emit('cart:item-added', {
product: {id: 123, name: 'Product A'},
quantity: 2
});5. API Request Lifecycle
// Before request
EventManager.on('api:request:before', (context) => {
// Show loading
showLoadingSpinner();
// Add headers
context.data.headers = {
...context.data.headers,
'Authorization': `Bearer ${getToken()}`
};
}, {
priority: 100
});
// Success
EventManager.on('api:request:success', async (context) => {
hideLoadingSpinner();
await EventManager.emit('notification:success', {
message: 'Request successful'
});
});
// Error
EventManager.on('api:request:error', async (context) => {
hideLoadingSpinner();
await EventManager.emit('notification:error', {
message: context.data.error.message
});
// Retry logic
if (context.data.error.code === 401) {
await refreshToken();
return {retry: true};
}
});
// After request (always)
EventManager.on('api:request:after', (context) => {
logApiCall(context.data);
});6. WebSocket Message Handler
// Connect WebSocket
const ws = new WebSocket('ws://localhost:8080');
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
// Forward as events
EventManager.emit(`ws:${message.type}`, message.data);
};
// Handle message types
EventManager.on('ws:user-online', (context) => {
updateUserStatus(context.data.userId, 'online');
});
EventManager.on('ws:new-message', (context) => {
displayMessage(context.data);
playNotificationSound();
});
EventManager.on('ws:typing', (context) => {
showTypingIndicator(context.data.userId);
});
// Pattern for logging
EventManager.on('ws:*', (context) => {
console.log('WebSocket event:', context.event, context.data);
});7. Theme Switcher
// Listen for theme changes
EventManager.on('theme:changed', (context) => {
const {theme} = context.data;
// Update DOM
document.body.className = theme;
// Save preference
localStorage.setItem('theme', theme);
// Update components
reloadComponents();
}, {
once: false
});
// Switch theme
async function switchTheme(theme) {
await EventManager.emit('theme:changed', {theme});
}
// Toggle dark mode
document.getElementById('dark-mode-toggle').addEventListener('click', () => {
const currentTheme = document.body.className;
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
switchTheme(newTheme);
});8. Data Synchronization
// Sync when data changes
EventManager.on('data:changed', async (context) => {
const {entity, data} = context.data;
// Sync to multiple destinations in parallel
await Promise.all([
syncToLocalStorage(entity, data),
syncToIndexedDB(entity, data),
syncToServer(entity, data)
]);
return {synced: true};
}, {
async: true,
timeout: 10000
});
// Conflict resolution
EventManager.on('data:conflict', async (context) => {
const {local, remote} = context.data;
// Show UI to resolve conflict
const resolved = await showConflictDialog(local, remote);
return {resolved};
}, {
async: true
});
// Usage
await EventManager.emit('data:changed', {
entity: 'user',
data: {id: 123, name: 'John Updated'}
});9. Feature Toggle System
// Define features
const features = new Map();
// Enable feature
EventManager.on('feature:enable', (context) => {
const {feature} = context.data;
features.set(feature, true);
// Reload affected components
reloadFeatureDependentComponents(feature);
console.log('Feature enabled:', feature);
});
// Disable feature
EventManager.on('feature:disable', (context) => {
const {feature} = context.data;
features.set(feature, false);
reloadFeatureDependentComponents(feature);
console.log('Feature disabled:', feature);
});
// Check feature
function isFeatureEnabled(feature) {
return features.get(feature) || false;
}
// Toggle
async function toggleFeature(feature) {
const enabled = isFeatureEnabled(feature);
const event = enabled ? 'feature:disable' : 'feature:enable';
await EventManager.emit(event, {feature});
}
// Use in code
if (isFeatureEnabled('dark-mode')) {
enableDarkMode();
}10. Performance Monitoring
// Track performance metrics
const performanceMetrics = [];
EventManager.use({
beforeEmit: (context) => {
context.startTime = performance.now();
},
afterEmit: (context) => {
const duration = performance.now() - context.startTime;
performanceMetrics.push({
event: context.event,
duration,
timestamp: context.timestamp,
listenerCount: context.results.length
});
// Warn if too slow
if (duration > 100) {
console.warn('Slow event:', context.event, `${duration}ms`);
}
}
});
// Report metrics
function getPerformanceReport() {
const avgDuration = performanceMetrics.reduce((sum, m) =>
sum + m.duration, 0) / performanceMetrics.length;
const slowEvents = performanceMetrics
.filter(m => m.duration > 100)
.sort((a, b) => b.duration - a.duration);
return {
totalEvents: performanceMetrics.length,
averageDuration: avgDuration,
slowEvents: slowEvents.slice(0, 10)
};
}
// View report
console.log('Performance Report:', getPerformanceReport());API Reference
Configuration
{
asyncTimeout: number, // Timeout for async handlers (ms) default: 5000
maxListeners: number, // Max listeners per event default: 2048
traceEvents: boolean, // Keep history for debug default: false
cleanup: {
enabled: boolean, // Enable auto cleanup default: true
interval: number, // Check every x ms default: 60000
maxAge: number, // Remove listeners older than x ms default: 1800000
batchSize: number // Process x items at a time default: 100
}
}Methods
Core Methods
// Initialize
init(options?: object): Promise<EventManager>
// Register listener
on(event: string, callback: function, options?: object): function
// Register one-time listener
once(event: string, callback: function, options?: object): function
// Emit event
emit(event: string, data?: object): Promise<array>
// Remove listener(s)
off(event: string, identifier?: string|function): boolean
// Emit to group
emitGroup(group: string, data?: object): Promise<array>Middleware Methods
// Add middleware
use(middleware: function|object): EventManager
// Run middleware hook
runMiddleware(hook: string, context: object): Promise<boolean>Pattern Methods
// Create regex pattern from string
createPattern(pattern: string): RegExp
// Add pattern listener
addPatternListener(pattern: string, callback: function, options?: object): function
// Remove pattern listener
removePatternListener(pattern: string, identifier?: string|function): boolean
// Check if string is pattern
isPattern(str: string): booleanGroup Methods
// Add listener to group
addToGroup(group: string, event: string, listener: object): void
// Remove listener from group
removeFromGroup(group: string, event: string, listener: object): voidCleanup Methods
// Manual cleanup
cleanup(): void
// Cleanup specific event
cleanupStaleListeners(event: string): void
// Clear all
clear(): void
// Destroy
destroy(): voidInfo Methods
// Get event info
getEventInfo(event: string): object
// Get debug info
getDebugInfo(): objectContext Object
{
event: string, // Event name
data: object, // Event data
timestamp: number, // Emit timestamp
preventDefault: boolean, // Prevent default flag
stopPropagation: boolean, // Stop propagation flag
stopImmediatePropagation: boolean,// Stop immediately flag
results: array, // Results from listeners
source: any, // Event source
path: array, // Event hierarchy path
currentPath: string // Current path in hierarchy
}Listener Options
{
priority: number, // Priority (higher = first) default: 0
once: boolean, // Run once and remove default: false
async: boolean, // Async handler default: false
group: string, // Group name default: null
timeout: number, // Async timeout (ms) default: 5000
maxRetries: number, // Max retry attempts default: 1
onError: function // Error handler default: null
}Best Practices
1. ✅ Use Namespaced Events
// ❌ Bad: generic names
EventManager.on('success', handler);
EventManager.on('error', handler);
// ✅ Good: clear names
EventManager.on('auth:login:success', handler);
EventManager.on('api:user:error', handler);2. ✅ Cleanup Listeners
// ❌ Bad: no cleanup
EventManager.on('data:updated', handler);
// ✅ Good: cleanup when done
const unsubscribe = EventManager.on('data:updated', handler);
// When component destroys
componentWillUnmount() {
unsubscribe();
}3. ✅ Use Once for One-time Events
// ❌ Bad: must off manually
EventManager.on('app:ready', handler);
EventManager.off('app:ready', handler);
// ✅ Good: auto-removed
EventManager.once('app:ready', handler);4. ✅ Use Priority Appropriately
// ❌ Bad: no priority
EventManager.on('form:submit', saveData);
EventManager.on('form:submit', validateData);
// ✅ Good: validate before save
EventManager.on('form:submit', validateData, {priority: 100});
EventManager.on('form:submit', saveData, {priority: 10});5. ✅ Handle Errors in Async
// ❌ Bad: no error handling
EventManager.on('data:fetch', async (context) => {
const data = await fetch(context.data.url);
return data.json();
}, {async: true});
// ✅ Good: with error handling
EventManager.on('data:fetch', async (context) => {
try {
const data = await fetch(context.data.url);
return await data.json();
} catch (error) {
console.error('Fetch error:', error);
return {error: error.message};
}
}, {async: true});6. ✅ Use Patterns Wisely
// ❌ Bad: register each event
EventManager.on('notification:info', handler);
EventManager.on('notification:success', handler);
EventManager.on('notification:warning', handler);
EventManager.on('notification:error', handler);
// ✅ Good: use pattern
EventManager.on('notification:*', handler);7. ✅ Use Groups for Related Events
// ✅ Good: group related events
EventManager.on('analytics:pageview', trackPageView, {
group: 'analytics'
});
EventManager.on('analytics:click', trackClick, {
group: 'analytics'
});
// Emit entire group
await EventManager.emitGroup('analytics', data);8. ✅ Document Event Names
// ✅ Good: write documentation
/**
* Events:
* - user:login - When user logs in successfully
* - user:logout - When user logs out
* - user:register - When user registers new account
*
* Data structure:
* {
* userId: number,
* username: string,
* email: string
* }
*/
const AuthEvents = {
LOGIN: 'user:login',
LOGOUT: 'user:logout',
REGISTER: 'user:register'
};
// Use constants
EventManager.on(AuthEvents.LOGIN, handler);9. ✅ Test Event Flow
// ✅ Good: write tests
describe('EventManager', () => {
it('should handle user login', async () => {
const handler = jest.fn();
EventManager.on('user:login', handler);
await EventManager.emit('user:login', {
userId: 123
});
expect(handler).toHaveBeenCalled();
expect(handler).toHaveBeenCalledWith(
expect.objectContaining({
event: 'user:login',
data: {userId: 123}
})
);
});
});10. ✅ Monitor Performance
// ✅ Good: track performance
EventManager.use({
beforeEmit: (context) => {
context._startTime = performance.now();
},
afterEmit: (context) => {
const duration = performance.now() - context._startTime;
if (duration > 100) {
console.warn('Slow event:', context.event, `${duration}ms`);
}
}
});Summary
When to Use EventManager
| Use Case | Use EventManager? | Notes |
|---|---|---|
| Communication between Components | ✅ Yes | Event Bus Pattern |
| Manage Application Events | ✅ Yes | Custom events |
| Decouple Modules | ✅ Yes | Loose coupling |
| Pattern Matching | ✅ Yes | Wildcards support |
| Event Groups | ✅ Yes | Built-in support |
| Middleware | ✅ Yes | Interceptors |
| Handle DOM Events | ❌ No | Use EventSystemManager |
| Event Delegation | ❌ No | Use EventSystemManager |
Key Features
| Feature | Supported | Notes |
|---|---|---|
| Event Bus | ✅ | Publish/Subscribe |
| Pattern Matching | ✅ | Wildcards, Regex |
| Event Hierarchy | ✅ | Dot notation |
| Event Groups | ✅ | Grouped events |
| Priority | ✅ | Ordered execution |
| Middleware | ✅ | beforeEmit, afterEmit |
| Async Support | ✅ | Promises, Async/Await |
| Context Control | ✅ | stopPropagation, preventDefault |
| Auto Cleanup | ✅ | Memory management |
| Debug Mode | ✅ | Event history |