Prerequisites
- Basic TypeScript syntax and types 📝
- Understanding of Promises and async programming ⚡
- Object-oriented programming concepts 🏗️
What you'll learn
- Master type-safe event emitters for robust event-driven architectures 📡
- Build scalable communication systems with custom event patterns 🌐
- Handle complex event flows with error resilience and performance optimization 🛡️
- Create production-ready event systems for real-world applications 🏭
🎯 Introduction
Welcome to the powerful world of type-safe event-driven programming with TypeScript! 🎉 In this guide, we’ll explore how to build robust communication systems that connect different parts of your application through elegant, type-safe events.
You’ll discover how to transform tightly-coupled code into loosely-coupled, maintainable systems that scale beautifully. Whether you’re building user interfaces 🖼️, real-time applications 📡, or complex business logic 🏢, mastering event emitters is essential for creating flexible, responsive TypeScript applications.
By the end of this tutorial, you’ll be architecting event-driven systems that communicate like a well-orchestrated symphony! 📡 Let’s dive in! 🏊♂️
📚 Understanding Event Emitters
🤔 What are Event Emitters?
Event emitters are like having a sophisticated messaging system within your application 📬. Think of them as the nervous system of your code - when something happens in one part, other parts can instantly react without being directly connected!
In TypeScript terms, event emitters provide:
- ✨ Decoupled communication - components communicate without direct dependencies
- 🚀 Type safety - compile-time checking for event names and payload types
- 🛡️ Flexible architecture - easy to add new listeners without modifying emitters
- 📦 Async support - handle both synchronous and asynchronous event processing
💡 Why Use Event Emitters?
Here’s why event emitters are architectural game-changers:
- Loose Coupling 🔗: Components don’t need to know about each other directly
- Scalability 📈: Easy to add new features without breaking existing code
- Testability 🧪: Mock events easily for isolated testing
- Maintainability 🔧: Changes in one component don’t cascade through the system
- Real-Time Features ⚡: Perfect for live updates and reactive interfaces
Real-world example: When a user places an order, instead of calling inventory, payment, and notification services directly, you emit an “order-placed” event and let each service react independently! 🛒
🔧 Basic Event Emitter Patterns
📝 Simple Type-Safe Event Emitter
Let’s start with fundamental event emitter patterns:
// 🎯 Basic type-safe event emitter implementation
// TypeScript ensures event names and payloads match exactly
// 🏗️ Define event payload types
interface EventMap {
'user-login': { userId: string; timestamp: Date; device: string };
'user-logout': { userId: string; sessionDuration: number };
'order-created': { orderId: string; userId: string; total: number; items: OrderItem[] };
'payment-processed': { orderId: string; amount: number; method: PaymentMethod };
'notification-sent': { userId: string; type: NotificationType; content: string };
}
// 🔧 Supporting types
interface OrderItem {
productId: string;
quantity: number;
price: number;
name: string;
}
type PaymentMethod = 'credit_card' | 'paypal' | 'bank_transfer' | 'crypto';
type NotificationType = 'email' | 'sms' | 'push' | 'in_app';
// 📡 Type-safe event emitter class
class TypeSafeEventEmitter<TEventMap = Record<string, any>> {
private listeners: Map<keyof TEventMap, Set<Function>> = new Map();
private onceListeners: Map<keyof TEventMap, Set<Function>> = new Map();
private maxListeners: number = 10;
// 👂 Add event listener with type safety
on<K extends keyof TEventMap>(
event: K,
listener: (payload: TEventMap[K]) => void | Promise<void>
): this {
console.log(`👂 Adding listener for event: ${String(event)}`);
if (!this.listeners.has(event)) {
this.listeners.set(event, new Set());
}
const eventListeners = this.listeners.get(event)!;
// 🚨 Check max listeners to prevent memory leaks
if (eventListeners.size >= this.maxListeners) {
console.warn(`⚠️ Too many listeners (${eventListeners.size}) for event: ${String(event)}`);
}
eventListeners.add(listener);
return this;
}
// 🎯 Add one-time listener
once<K extends keyof TEventMap>(
event: K,
listener: (payload: TEventMap[K]) => void | Promise<void>
): this {
console.log(`🎯 Adding one-time listener for event: ${String(event)}`);
if (!this.onceListeners.has(event)) {
this.onceListeners.set(event, new Set());
}
this.onceListeners.get(event)!.add(listener);
return this;
}
// 📤 Emit event with type-safe payload
async emit<K extends keyof TEventMap>(event: K, payload: TEventMap[K]): Promise<void> {
console.log(`📤 Emitting event: ${String(event)}`);
const promises: Promise<void>[] = [];
// 🔄 Execute regular listeners
const regularListeners = this.listeners.get(event);
if (regularListeners) {
for (const listener of regularListeners) {
promises.push(this.executeListener(listener, payload));
}
}
// 🎯 Execute one-time listeners and remove them
const onceListeners = this.onceListeners.get(event);
if (onceListeners) {
for (const listener of onceListeners) {
promises.push(this.executeListener(listener, payload));
}
this.onceListeners.delete(event); // 🗑️ Remove after execution
}
// ⏳ Wait for all listeners to complete
await Promise.all(promises);
console.log(`✅ Event ${String(event)} processing complete`);
}
// ⚡ Execute listener with error handling
private async executeListener(listener: Function, payload: any): Promise<void> {
try {
const result = listener(payload);
// 🔄 Handle async listeners
if (result instanceof Promise) {
await result;
}
} catch (error) {
console.error('💥 Listener execution error:', error.message);
// 🛡️ Don't let one listener failure break others
}
}
// 🗑️ Remove listener
off<K extends keyof TEventMap>(
event: K,
listener: (payload: TEventMap[K]) => void | Promise<void>
): this {
console.log(`🗑️ Removing listener for event: ${String(event)}`);
const eventListeners = this.listeners.get(event);
if (eventListeners) {
eventListeners.delete(listener);
// 🧹 Clean up empty sets
if (eventListeners.size === 0) {
this.listeners.delete(event);
}
}
return this;
}
// 🧹 Remove all listeners for an event
removeAllListeners<K extends keyof TEventMap>(event?: K): this {
if (event) {
console.log(`🧹 Removing all listeners for event: ${String(event)}`);
this.listeners.delete(event);
this.onceListeners.delete(event);
} else {
console.log('🧹 Removing all listeners for all events');
this.listeners.clear();
this.onceListeners.clear();
}
return this;
}
// 📊 Get listener count
listenerCount<K extends keyof TEventMap>(event: K): number {
const regular = this.listeners.get(event)?.size || 0;
const once = this.onceListeners.get(event)?.size || 0;
return regular + once;
}
// ⚙️ Set max listeners
setMaxListeners(n: number): this {
this.maxListeners = n;
return this;
}
}
// 🎮 Usage example with type safety
const eventBus = new TypeSafeEventEmitter<EventMap>();
// ✅ Type-safe event listening
eventBus.on('user-login', async (data) => {
// 🎯 TypeScript knows data has userId, timestamp, device properties
console.log(`👤 User ${data.userId} logged in from ${data.device}`);
// 📊 Track login analytics
await trackUserLogin(data.userId, data.device);
});
eventBus.on('order-created', async (order) => {
// 🛒 TypeScript knows order has orderId, userId, total, items properties
console.log(`🛒 New order ${order.orderId} for $${order.total}`);
// 📧 Send confirmation email
await sendOrderConfirmation(order.userId, order.orderId);
// 📦 Update inventory
await updateInventory(order.items);
});
// 🚫 TypeScript will catch type errors at compile time
// eventBus.emit('user-login', { invalidProperty: 'value' }); // ❌ Compile error!
🔄 Advanced Event Flow Patterns
Let’s explore sophisticated event patterns:
// 🏭 Advanced event-driven system with middleware and validation
class AdvancedEventEmitter<TEventMap = Record<string, any>> extends TypeSafeEventEmitter<TEventMap> {
private middleware: EventMiddleware<TEventMap>[] = [];
private validators: Map<keyof TEventMap, EventValidator<any>> = new Map();
private eventHistory: EventHistoryItem<TEventMap>[] = [];
private maxHistory: number = 1000;
// 🔧 Add middleware for event processing
use(middleware: EventMiddleware<TEventMap>): this {
console.log('🔧 Adding event middleware');
this.middleware.push(middleware);
return this;
}
// ✅ Add validator for specific event type
setValidator<K extends keyof TEventMap>(
event: K,
validator: EventValidator<TEventMap[K]>
): this {
console.log(`✅ Setting validator for event: ${String(event)}`);
this.validators.set(event, validator);
return this;
}
// 📤 Enhanced emit with middleware and validation
async emit<K extends keyof TEventMap>(event: K, payload: TEventMap[K]): Promise<void> {
const eventData: EventContext<TEventMap, K> = {
event,
payload,
timestamp: new Date(),
metadata: {
emitterId: this.generateEmitterId(),
source: 'advanced-emitter'
}
};
try {
// ✅ Validate payload
await this.validateEvent(event, payload);
// 🔧 Run through middleware pipeline
const processedData = await this.runMiddleware(eventData);
// 📊 Record in history
this.recordEvent(processedData);
// 📤 Emit the processed event
await super.emit(event, processedData.payload);
} catch (error) {
console.error(`💥 Event emission failed for ${String(event)}:`, error.message);
// 📤 Emit error event
await this.emitError(event, payload, error);
throw error;
}
}
// ✅ Validate event payload
private async validateEvent<K extends keyof TEventMap>(
event: K,
payload: TEventMap[K]
): Promise<void> {
const validator = this.validators.get(event);
if (validator) {
console.log(`✅ Validating event: ${String(event)}`);
const result = await validator.validate(payload);
if (!result.isValid) {
throw new Error(`Validation failed: ${result.errors.join(', ')}`);
}
}
}
// 🔧 Run middleware pipeline
private async runMiddleware<K extends keyof TEventMap>(
eventData: EventContext<TEventMap, K>
): Promise<EventContext<TEventMap, K>> {
let currentData = eventData;
for (const middleware of this.middleware) {
console.log('🔧 Running middleware...');
currentData = await middleware.process(currentData);
}
return currentData;
}
// 📊 Record event in history
private recordEvent<K extends keyof TEventMap>(eventData: EventContext<TEventMap, K>): void {
this.eventHistory.push({
event: eventData.event,
timestamp: eventData.timestamp,
metadata: eventData.metadata
});
// 🧹 Maintain history size limit
if (this.eventHistory.length > this.maxHistory) {
this.eventHistory = this.eventHistory.slice(-this.maxHistory);
}
}
// 💥 Emit error event
private async emitError<K extends keyof TEventMap>(
originalEvent: K,
payload: TEventMap[K],
error: Error
): Promise<void> {
try {
// 📤 Emit system error event (if it exists in the event map)
if ('system-error' in this.listeners || 'system-error' in this.onceListeners) {
await super.emit('system-error' as K, {
originalEvent: String(originalEvent),
error: error.message,
timestamp: new Date()
} as TEventMap[K]);
}
} catch (emitError) {
console.error('💥 Failed to emit error event:', emitError.message);
}
}
// 📊 Generate unique emitter ID
private generateEmitterId(): string {
return `emitter-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}
// 📜 Get event history
getEventHistory(): EventHistoryItem<TEventMap>[] {
return [...this.eventHistory];
}
// 📊 Get event statistics
getEventStats(): EventStats<TEventMap> {
const stats: EventStats<TEventMap> = {} as EventStats<TEventMap>;
for (const historyItem of this.eventHistory) {
const event = historyItem.event;
if (!stats[event]) {
stats[event] = { count: 0, lastEmitted: historyItem.timestamp };
}
stats[event].count++;
if (historyItem.timestamp > stats[event].lastEmitted) {
stats[event].lastEmitted = historyItem.timestamp;
}
}
return stats;
}
}
// 🏗️ Supporting interfaces
interface EventContext<TEventMap, K extends keyof TEventMap> {
event: K;
payload: TEventMap[K];
timestamp: Date;
metadata: EventMetadata;
}
interface EventMetadata {
emitterId: string;
source: string;
[key: string]: any;
}
interface EventMiddleware<TEventMap> {
process<K extends keyof TEventMap>(
eventData: EventContext<TEventMap, K>
): Promise<EventContext<TEventMap, K>>;
}
interface EventValidator<T> {
validate(payload: T): Promise<ValidationResult>;
}
interface ValidationResult {
isValid: boolean;
errors: string[];
}
interface EventHistoryItem<TEventMap> {
event: keyof TEventMap;
timestamp: Date;
metadata: EventMetadata;
}
type EventStats<TEventMap> = {
[K in keyof TEventMap]: {
count: number;
lastEmitted: Date;
};
};
// 🔧 Concrete middleware implementations
class LoggingMiddleware<TEventMap> implements EventMiddleware<TEventMap> {
async process<K extends keyof TEventMap>(
eventData: EventContext<TEventMap, K>
): Promise<EventContext<TEventMap, K>> {
console.log(`📝 [MIDDLEWARE] Event ${String(eventData.event)} at ${eventData.timestamp.toISOString()}`);
return eventData;
}
}
class EnrichmentMiddleware<TEventMap> implements EventMiddleware<TEventMap> {
async process<K extends keyof TEventMap>(
eventData: EventContext<TEventMap, K>
): Promise<EventContext<TEventMap, K>> {
console.log('✨ [MIDDLEWARE] Enriching event data...');
// 📊 Add enrichment data
eventData.metadata.enrichedAt = new Date();
eventData.metadata.processingId = `proc-${Math.random().toString(36).substr(2, 9)}`;
return eventData;
}
}
// ✅ Concrete validator implementation
class UserLoginValidator implements EventValidator<EventMap['user-login']> {
async validate(payload: EventMap['user-login']): Promise<ValidationResult> {
const errors: string[] = [];
// 🔍 Validate user ID
if (!payload.userId || payload.userId.trim() === '') {
errors.push('userId is required');
}
// 🔍 Validate device
if (!payload.device || payload.device.trim() === '') {
errors.push('device is required');
}
// 🔍 Validate timestamp
if (!payload.timestamp || payload.timestamp > new Date()) {
errors.push('timestamp must be valid and not in the future');
}
return {
isValid: errors.length === 0,
errors
};
}
}
🌐 Real-World Event Systems
🏢 Enterprise Event Bus
Let’s build a comprehensive enterprise-grade event system:
// 🏢 Enterprise event bus with namespacing and routing
class EnterpriseEventBus {
private namespaces: Map<string, AdvancedEventEmitter<any>> = new Map();
private globalEmitter: AdvancedEventEmitter<GlobalEventMap>;
private routingRules: RoutingRule[] = [];
private eventQueue: QueuedEvent[] = [];
private isProcessingQueue = false;
constructor() {
this.globalEmitter = new AdvancedEventEmitter<GlobalEventMap>();
this.setupGlobalMiddleware();
}
// 🌐 Get or create namespace
namespace<T = Record<string, any>>(name: string): AdvancedEventEmitter<T> {
if (!this.namespaces.has(name)) {
console.log(`🌐 Creating namespace: ${name}`);
const emitter = new AdvancedEventEmitter<T>();
this.setupNamespaceIntegration(name, emitter);
this.namespaces.set(name, emitter);
}
return this.namespaces.get(name)! as AdvancedEventEmitter<T>;
}
// 🔀 Add routing rule for cross-namespace communication
addRoutingRule(rule: RoutingRule): this {
console.log(`🔀 Adding routing rule: ${rule.from.namespace}.${rule.from.event} → ${rule.to.namespace}.${rule.to.event}`);
this.routingRules.push(rule);
return this;
}
// 📤 Broadcast event to all namespaces
async broadcast<T>(event: string, payload: T): Promise<void> {
console.log(`📢 Broadcasting event: ${event}`);
const promises: Promise<void>[] = [];
// 📡 Send to global emitter
promises.push(this.globalEmitter.emit('global-broadcast' as any, {
event,
payload,
timestamp: new Date()
}));
// 📡 Send to all namespaces
for (const [namespaceName, emitter] of this.namespaces) {
promises.push(
emitter.emit(event as any, payload).catch(error => {
console.error(`💥 Broadcast failed for namespace ${namespaceName}:`, error.message);
})
);
}
await Promise.all(promises);
}
// 📊 Get system metrics
getMetrics(): SystemMetrics {
const namespaceMetrics: Record<string, any> = {};
for (const [name, emitter] of this.namespaces) {
namespaceMetrics[name] = emitter.getEventStats();
}
return {
totalNamespaces: this.namespaces.size,
globalEvents: this.globalEmitter.getEventStats(),
namespaces: namespaceMetrics,
routingRules: this.routingRules.length,
queuedEvents: this.eventQueue.length
};
}
// 🔧 Setup global middleware
private setupGlobalMiddleware(): void {
this.globalEmitter.use(new LoggingMiddleware());
this.globalEmitter.use(new EnrichmentMiddleware());
}
// 🌐 Setup namespace integration
private setupNamespaceIntegration(namespaceName: string, emitter: AdvancedEventEmitter<any>): void {
// 🔗 Intercept namespace events for routing
const originalEmit = emitter.emit.bind(emitter);
emitter.emit = async (event: any, payload: any) => {
// 🔀 Check routing rules
await this.processRoutingRules(namespaceName, event, payload);
// 📤 Emit original event
return originalEmit(event, payload);
};
}
// 🔀 Process routing rules
private async processRoutingRules(sourceNamespace: string, sourceEvent: string, payload: any): Promise<void> {
for (const rule of this.routingRules) {
if (rule.from.namespace === sourceNamespace && rule.from.event === sourceEvent) {
console.log(`🔀 Routing ${sourceNamespace}.${sourceEvent} → ${rule.to.namespace}.${rule.to.event}`);
try {
const targetEmitter = this.namespace(rule.to.namespace);
// 🔄 Transform payload if transformer provided
const transformedPayload = rule.transformer
? await rule.transformer(payload)
: payload;
await targetEmitter.emit(rule.to.event as any, transformedPayload);
} catch (error) {
console.error(`💥 Routing failed:`, error.message);
}
}
}
}
// 📦 Queue event for later processing
queueEvent(namespaceName: string, event: string, payload: any, delay?: number): void {
console.log(`📦 Queueing event: ${namespaceName}.${event}`);
this.eventQueue.push({
namespaceName,
event,
payload,
scheduledAt: new Date(Date.now() + (delay || 0)),
id: this.generateEventId()
});
this.processQueue();
}
// ⚡ Process event queue
private async processQueue(): Promise<void> {
if (this.isProcessingQueue) return;
this.isProcessingQueue = true;
try {
while (this.eventQueue.length > 0) {
const now = new Date();
const dueEvents = this.eventQueue.filter(e => e.scheduledAt <= now);
if (dueEvents.length === 0) {
// ⏳ Wait for next due event
const nextEvent = this.eventQueue.reduce((earliest, current) =>
current.scheduledAt < earliest.scheduledAt ? current : earliest
);
const delay = nextEvent.scheduledAt.getTime() - now.getTime();
setTimeout(() => this.processQueue(), delay);
break;
}
// 📤 Process due events
for (const queuedEvent of dueEvents) {
try {
const emitter = this.namespace(queuedEvent.namespaceName);
await emitter.emit(queuedEvent.event as any, queuedEvent.payload);
// 🗑️ Remove from queue
this.eventQueue = this.eventQueue.filter(e => e.id !== queuedEvent.id);
} catch (error) {
console.error(`💥 Queued event processing failed:`, error.message);
}
}
}
} finally {
this.isProcessingQueue = false;
}
}
// 🆔 Generate unique event ID
private generateEventId(): string {
return `event-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}
}
// 🏗️ Supporting interfaces for enterprise bus
interface GlobalEventMap {
'global-broadcast': { event: string; payload: any; timestamp: Date };
'system-error': { error: string; timestamp: Date; source: string };
'system-shutdown': { reason: string; timestamp: Date };
}
interface RoutingRule {
from: { namespace: string; event: string };
to: { namespace: string; event: string };
transformer?: (payload: any) => Promise<any> | any;
}
interface QueuedEvent {
id: string;
namespaceName: string;
event: string;
payload: any;
scheduledAt: Date;
}
interface SystemMetrics {
totalNamespaces: number;
globalEvents: any;
namespaces: Record<string, any>;
routingRules: number;
queuedEvents: number;
}
// 🏢 Real-world usage example
class ECommerceEventSystem {
private eventBus: EnterpriseEventBus;
private userEvents: AdvancedEventEmitter<UserEventMap>;
private orderEvents: AdvancedEventEmitter<OrderEventMap>;
private inventoryEvents: AdvancedEventEmitter<InventoryEventMap>;
private notificationEvents: AdvancedEventEmitter<NotificationEventMap>;
constructor() {
this.eventBus = new EnterpriseEventBus();
// 🌐 Setup namespaces
this.userEvents = this.eventBus.namespace<UserEventMap>('users');
this.orderEvents = this.eventBus.namespace<OrderEventMap>('orders');
this.inventoryEvents = this.eventBus.namespace<InventoryEventMap>('inventory');
this.notificationEvents = this.eventBus.namespace<NotificationEventMap>('notifications');
this.setupEventRouting();
this.setupEventHandlers();
}
// 🔀 Setup cross-system event routing
private setupEventRouting(): void {
// 🛒 Order created → Update inventory
this.eventBus.addRoutingRule({
from: { namespace: 'orders', event: 'order-created' },
to: { namespace: 'inventory', event: 'reserve-items' },
transformer: (order: OrderEventMap['order-created']) => ({
orderId: order.id,
items: order.items.map(item => ({
productId: item.productId,
quantity: item.quantity
}))
})
});
// 🛒 Order created → Send notification
this.eventBus.addRoutingRule({
from: { namespace: 'orders', event: 'order-created' },
to: { namespace: 'notifications', event: 'send-order-confirmation' },
transformer: (order: OrderEventMap['order-created']) => ({
userId: order.userId,
orderId: order.id,
total: order.total
})
});
// 👤 User registered → Send welcome notification
this.eventBus.addRoutingRule({
from: { namespace: 'users', event: 'user-registered' },
to: { namespace: 'notifications', event: 'send-welcome-email' },
transformer: (user: UserEventMap['user-registered']) => ({
userId: user.id,
email: user.email,
name: user.name
})
});
}
// 🎛️ Setup event handlers
private setupEventHandlers(): void {
// 👤 User event handlers
this.userEvents.on('user-registered', async (user) => {
console.log(`👤 Processing user registration: ${user.email}`);
await this.createUserProfile(user);
await this.setupUserPreferences(user.id);
});
// 🛒 Order event handlers
this.orderEvents.on('order-created', async (order) => {
console.log(`🛒 Processing order: ${order.id}`);
await this.validateOrder(order);
await this.processPayment(order);
});
this.orderEvents.on('payment-failed', async (failure) => {
console.log(`💳 Payment failed for order: ${failure.orderId}`);
await this.handlePaymentFailure(failure);
});
// 📦 Inventory event handlers
this.inventoryEvents.on('reserve-items', async (reservation) => {
console.log(`📦 Reserving items for order: ${reservation.orderId}`);
await this.reserveInventory(reservation);
});
this.inventoryEvents.on('low-stock-alert', async (alert) => {
console.log(`⚠️ Low stock alert: ${alert.productId}`);
await this.handleLowStock(alert);
});
// 📧 Notification event handlers
this.notificationEvents.on('send-order-confirmation', async (notification) => {
console.log(`📧 Sending order confirmation: ${notification.orderId}`);
await this.sendOrderConfirmationEmail(notification);
});
this.notificationEvents.on('send-welcome-email', async (welcome) => {
console.log(`📧 Sending welcome email: ${welcome.email}`);
await this.sendWelcomeEmail(welcome);
});
}
// 🚀 Public methods for triggering events
async registerUser(userData: Omit<UserEventMap['user-registered'], 'registeredAt'>): Promise<void> {
await this.userEvents.emit('user-registered', {
...userData,
registeredAt: new Date()
});
}
async createOrder(orderData: Omit<OrderEventMap['order-created'], 'createdAt'>): Promise<void> {
await this.orderEvents.emit('order-created', {
...orderData,
createdAt: new Date()
});
}
async reportLowStock(productId: string, currentStock: number, threshold: number): Promise<void> {
await this.inventoryEvents.emit('low-stock-alert', {
productId,
currentStock,
threshold,
alertedAt: new Date()
});
}
// 🛠️ Business logic methods (simplified for example)
private async createUserProfile(user: UserEventMap['user-registered']): Promise<void> {
console.log(`🔧 Creating user profile for: ${user.email}`);
// Database operations would go here
}
private async setupUserPreferences(userId: string): Promise<void> {
console.log(`⚙️ Setting up preferences for user: ${userId}`);
// Default preferences setup would go here
}
private async validateOrder(order: OrderEventMap['order-created']): Promise<void> {
console.log(`✅ Validating order: ${order.id}`);
// Order validation logic would go here
}
private async processPayment(order: OrderEventMap['order-created']): Promise<void> {
console.log(`💳 Processing payment for order: ${order.id}`);
// Simulate payment processing
const success = Math.random() > 0.1; // 90% success rate
if (success) {
await this.orderEvents.emit('payment-processed', {
orderId: order.id,
amount: order.total,
method: 'credit_card',
processedAt: new Date()
});
} else {
await this.orderEvents.emit('payment-failed', {
orderId: order.id,
amount: order.total,
reason: 'Insufficient funds',
failedAt: new Date()
});
}
}
private async handlePaymentFailure(failure: OrderEventMap['payment-failed']): Promise<void> {
console.log(`🚨 Handling payment failure: ${failure.reason}`);
// Send failure notification
await this.notificationEvents.emit('send-payment-failure-notification', {
orderId: failure.orderId,
reason: failure.reason
});
}
private async reserveInventory(reservation: InventoryEventMap['reserve-items']): Promise<void> {
console.log(`📦 Reserving inventory for order: ${reservation.orderId}`);
// Inventory reservation logic would go here
}
private async handleLowStock(alert: InventoryEventMap['low-stock-alert']): Promise<void> {
console.log(`⚠️ Handling low stock for product: ${alert.productId}`);
// Auto-reorder if below critical threshold
if (alert.currentStock < alert.threshold * 0.5) {
console.log(`🔄 Auto-reordering product: ${alert.productId}`);
}
}
private async sendOrderConfirmationEmail(notification: NotificationEventMap['send-order-confirmation']): Promise<void> {
console.log(`📬 Sending order confirmation email for order: ${notification.orderId}`);
// Email sending logic would go here
}
private async sendWelcomeEmail(welcome: NotificationEventMap['send-welcome-email']): Promise<void> {
console.log(`📬 Sending welcome email to: ${welcome.email}`);
// Welcome email logic would go here
}
// 📊 Get system metrics
getSystemMetrics(): SystemMetrics {
return this.eventBus.getMetrics();
}
}
// 🏗️ Event map definitions for different domains
interface UserEventMap {
'user-registered': {
id: string;
email: string;
name: string;
registeredAt: Date;
};
'user-login': {
userId: string;
loginAt: Date;
ipAddress: string;
};
'user-logout': {
userId: string;
sessionDuration: number;
};
}
interface OrderEventMap {
'order-created': {
id: string;
userId: string;
items: Array<{
productId: string;
quantity: number;
price: number;
}>;
total: number;
createdAt: Date;
};
'payment-processed': {
orderId: string;
amount: number;
method: string;
processedAt: Date;
};
'payment-failed': {
orderId: string;
amount: number;
reason: string;
failedAt: Date;
};
}
interface InventoryEventMap {
'reserve-items': {
orderId: string;
items: Array<{
productId: string;
quantity: number;
}>;
};
'low-stock-alert': {
productId: string;
currentStock: number;
threshold: number;
alertedAt: Date;
};
}
interface NotificationEventMap {
'send-order-confirmation': {
userId: string;
orderId: string;
total: number;
};
'send-welcome-email': {
userId: string;
email: string;
name: string;
};
'send-payment-failure-notification': {
orderId: string;
reason: string;
};
}
🎯 Performance & Best Practices
⚡ Optimizing Event Performance
Here are essential patterns for high-performance event systems:
// 🚀 High-performance event emitter with batching and throttling
class PerformantEventEmitter<TEventMap = Record<string, any>> extends AdvancedEventEmitter<TEventMap> {
private batchConfig: Map<keyof TEventMap, BatchConfig> = new Map();
private batchQueues: Map<keyof TEventMap, BatchQueue<any>> = new Map();
private throttleConfig: Map<keyof TEventMap, ThrottleConfig> = new Map();
private throttleState: Map<keyof TEventMap, ThrottleState> = new Map();
// 📦 Configure event batching
configureBatching<K extends keyof TEventMap>(
event: K,
config: BatchConfig
): this {
console.log(`📦 Configuring batching for event: ${String(event)}`);
this.batchConfig.set(event, config);
this.batchQueues.set(event, {
items: [],
timer: null
});
return this;
}
// 🔄 Configure event throttling
configureThrottling<K extends keyof TEventMap>(
event: K,
config: ThrottleConfig
): this {
console.log(`🔄 Configuring throttling for event: ${String(event)}`);
this.throttleConfig.set(event, config);
this.throttleState.set(event, {
lastEmitted: 0,
pending: null
});
return this;
}
// 📤 Enhanced emit with batching and throttling
async emit<K extends keyof TEventMap>(event: K, payload: TEventMap[K]): Promise<void> {
// 📦 Handle batching
if (this.batchConfig.has(event)) {
return this.handleBatchedEmit(event, payload);
}
// 🔄 Handle throttling
if (this.throttleConfig.has(event)) {
return this.handleThrottledEmit(event, payload);
}
// 📤 Regular emit
return super.emit(event, payload);
}
// 📦 Handle batched event emission
private async handleBatchedEmit<K extends keyof TEventMap>(
event: K,
payload: TEventMap[K]
): Promise<void> {
const config = this.batchConfig.get(event)!;
const queue = this.batchQueues.get(event)!;
// 📦 Add to batch
queue.items.push(payload);
// 🚀 Process immediately if batch size reached
if (queue.items.length >= config.size) {
return this.processBatch(event);
}
// ⏰ Set timer for batch processing if not already set
if (!queue.timer) {
queue.timer = setTimeout(() => {
this.processBatch(event);
}, config.timeout);
}
}
// 📦 Process batched events
private async processBatch<K extends keyof TEventMap>(event: K): Promise<void> {
const queue = this.batchQueues.get(event)!;
if (queue.items.length === 0) return;
const batchItems = [...queue.items];
queue.items = [];
// 🧹 Clear timer
if (queue.timer) {
clearTimeout(queue.timer);
queue.timer = null;
}
console.log(`📦 Processing batch of ${batchItems.length} events for: ${String(event)}`);
// 📤 Emit batch event
await super.emit(event, batchItems as TEventMap[K]);
}
// 🔄 Handle throttled event emission
private async handleThrottledEmit<K extends keyof TEventMap>(
event: K,
payload: TEventMap[K]
): Promise<void> {
const config = this.throttleConfig.get(event)!;
const state = this.throttleState.get(event)!;
const now = Date.now();
// ⚡ Check if we can emit immediately
if (now - state.lastEmitted >= config.interval) {
state.lastEmitted = now;
return super.emit(event, payload);
}
// 🔄 Handle pending emission
if (config.strategy === 'leading') {
// 🔄 Ignore if already pending
return;
} else if (config.strategy === 'trailing') {
// 🔄 Schedule trailing emission
if (state.pending) {
clearTimeout(state.pending);
}
const delay = config.interval - (now - state.lastEmitted);
state.pending = setTimeout(async () => {
state.lastEmitted = Date.now();
state.pending = null;
await super.emit(event, payload);
}, delay);
}
}
// 🧹 Cleanup method
destroy(): void {
// 🧹 Clear all batch timers
for (const queue of this.batchQueues.values()) {
if (queue.timer) {
clearTimeout(queue.timer);
}
}
// 🧹 Clear all throttle timers
for (const state of this.throttleState.values()) {
if (state.pending) {
clearTimeout(state.pending);
}
}
// 🧹 Clear all listeners
this.removeAllListeners();
}
}
// 🏗️ Performance configuration interfaces
interface BatchConfig {
size: number; // Maximum batch size
timeout: number; // Maximum wait time (ms)
}
interface ThrottleConfig {
interval: number; // Throttle interval (ms)
strategy: 'leading' | 'trailing'; // Throttle strategy
}
interface BatchQueue<T> {
items: T[];
timer: NodeJS.Timeout | null;
}
interface ThrottleState {
lastEmitted: number;
pending: NodeJS.Timeout | null;
}
// ⚡ Memory-efficient event emitter with weak references
class MemoryEfficientEventEmitter<TEventMap = Record<string, any>> extends TypeSafeEventEmitter<TEventMap> {
private weakListeners: Map<keyof TEventMap, WeakSet<object>> = new Map();
private listenerCallbacks: Map<object, Function> = new Map();
// 👂 Add weak listener (automatically removed when object is garbage collected)
onWeak<K extends keyof TEventMap>(
event: K,
listener: (payload: TEventMap[K]) => void | Promise<void>,
target: object
): this {
console.log(`👂 Adding weak listener for event: ${String(event)}`);
if (!this.weakListeners.has(event)) {
this.weakListeners.set(event, new WeakSet());
}
this.weakListeners.get(event)!.add(target);
this.listenerCallbacks.set(target, listener);
return this;
}
// 📤 Enhanced emit with weak reference support
async emit<K extends keyof TEventMap>(event: K, payload: TEventMap[K]): Promise<void> {
// 📤 Emit to regular listeners
await super.emit(event, payload);
// 📤 Emit to weak listeners
const weakSet = this.weakListeners.get(event);
if (weakSet) {
const promises: Promise<void>[] = [];
for (const [target, callback] of this.listenerCallbacks) {
if (weakSet.has(target)) {
promises.push(this.executeListener(callback, payload));
}
}
await Promise.all(promises);
}
}
// 🧹 Cleanup orphaned weak references
cleanupWeakReferences(): void {
console.log('🧹 Cleaning up orphaned weak references...');
// This would require additional tracking mechanism in real implementation
// For demonstration purposes, we show the concept
}
}
// 📊 Event metrics and monitoring
class MonitoredEventEmitter<TEventMap = Record<string, any>> extends PerformantEventEmitter<TEventMap> {
private metrics: EventMetrics = {
totalEvents: 0,
eventsPerSecond: 0,
averageListenerCount: 0,
errorRate: 0,
memoryUsage: 0
};
private metricsInterval: NodeJS.Timeout;
private eventCounts: number[] = [];
private errorCounts: number[] = [];
constructor() {
super();
this.startMetricsCollection();
}
// 📤 Monitored emit with metrics collection
async emit<K extends keyof TEventMap>(event: K, payload: TEventMap[K]): Promise<void> {
const startTime = process.hrtime.bigint();
try {
await super.emit(event, payload);
this.metrics.totalEvents++;
} catch (error) {
this.errorCounts.push(1);
throw error;
} finally {
const endTime = process.hrtime.bigint();
const duration = Number(endTime - startTime) / 1000000; // Convert to ms
this.recordEventDuration(String(event), duration);
}
}
// 📊 Start metrics collection
private startMetricsCollection(): void {
this.metricsInterval = setInterval(() => {
this.updateMetrics();
}, 1000); // Update metrics every second
}
// 📊 Update performance metrics
private updateMetrics(): void {
// 📈 Calculate events per second
this.eventCounts.push(this.metrics.totalEvents);
if (this.eventCounts.length > 60) { // Keep last 60 seconds
this.eventCounts.shift();
}
if (this.eventCounts.length >= 2) {
const recentEvents = this.eventCounts[this.eventCounts.length - 1] - this.eventCounts[this.eventCounts.length - 2];
this.metrics.eventsPerSecond = recentEvents;
}
// 📊 Calculate error rate
const totalErrors = this.errorCounts.reduce((sum, count) => sum + count, 0);
this.metrics.errorRate = this.metrics.totalEvents > 0 ? totalErrors / this.metrics.totalEvents : 0;
// 💾 Estimate memory usage
this.metrics.memoryUsage = process.memoryUsage().heapUsed;
}
// ⏱️ Record event duration
private recordEventDuration(event: string, duration: number): void {
// In a real implementation, this would store duration metrics
// for performance analysis and optimization
}
// 📊 Get current metrics
getMetrics(): EventMetrics {
return { ...this.metrics };
}
// 🧹 Cleanup
destroy(): void {
if (this.metricsInterval) {
clearInterval(this.metricsInterval);
}
super.destroy();
}
}
// 🏗️ Metrics interface
interface EventMetrics {
totalEvents: number;
eventsPerSecond: number;
averageListenerCount: number;
errorRate: number;
memoryUsage: number;
}
🏆 Summary
Congratulations! 🎉 You’ve mastered event emitters and type-safe event-driven programming in TypeScript! Here’s what you’ve accomplished:
🎯 Key Takeaways
- Type Safety 🛡️: Build event systems with compile-time guarantees for event names and payloads
- Scalable Architecture 🏗️: Create loosely-coupled systems that grow with your application
- Performance Optimization 🚀: Implement batching, throttling, and memory-efficient patterns
- Enterprise Patterns 🏢: Build production-ready event systems with routing and namespacing
- Real-World Applications 🌐: Create complex business logic with elegant event flows
🛠️ What You Can Build
- Microservice Communication 📡: Type-safe inter-service messaging
- Real-Time Applications ⚡: Live dashboards and collaborative tools
- Event-Driven Architectures 🏗️: Scalable business process automation
- User Interface Systems 🖼️: Reactive component communication
- IoT Data Processing 📊: Device event aggregation and processing
🚀 Next Steps
Ready to take your event-driven skills even further? Consider exploring:
- WebSocket Integration 🌐: Real-time client-server communication
- Message Queues 📬: Distributed event processing with Redis/RabbitMQ
- Event Sourcing 📜: Store application state as sequence of events
- CQRS Patterns 🔄: Command Query Responsibility Segregation
You’re now equipped to build sophisticated, scalable event-driven applications that handle complex business logic with elegance and type safety! 🎯 Keep emitting and listening! 📡✨