Type Safe Event Emitter
Generic event emitter with type-safe event handling
Code
type EventMap = Record<string, any>;
type EventKey<T extends EventMap> = string & keyof T;
type EventCallback<T> = (data: T) => void;
class TypedEventEmitter<T extends EventMap> {
private listeners: {
[K in keyof T]?: EventCallback<T[K]>[];
} = {};
// Subscribe to an event
on<K extends EventKey<T>>(
eventName: K,
callback: EventCallback<T[K]>
): () => void {
__TOKEN_40__ (!this.listeners[eventName]) {
this.listeners[eventName] = [];
}
this.listeners[eventName]!.push(callback);
// Return unsubscribe function
__TOKEN_44__ () => this.off(eventName, callback);
}
// Subscribe once
once<K extends EventKey<T>>(
eventName: K,
callback: EventCallback<T[K]>
): () => void {
const onceCallback: EventCallback<T[K]> = (data) => {
callback(data);
this.off(eventName, onceCallback);
};
return this.on(eventName, onceCallback);
}
// Unsubscribe
off<K extends EventKey<T>>(
eventName: K,
callback: EventCallback<T[K]>
): void {
const callbacks = this.listeners[eventName];
__TOKEN_54__ (!callbacks) return;
const index = callbacks.indexOf(callback);
__TOKEN_57__ (index > -1) {
callbacks.splice(index, 1);
}
}
// Emit event
emit<K extends EventKey<T>>(eventName: K, data: T[K]): void {
const callbacks = this.listeners[eventName];
__TOKEN_61__ (!callbacks) return;
// Create a copy to avoid issues if callbacks are removed during iteration
callbacks.slice().forEach(callback => {
try {
callback(data);
} __TOKEN_64__ (error) {
console.error(`Error in event listener for ${eventName}:`, error);
}
});
}
// Remove all listeners for an event
removeAllListeners<K extends EventKey<T>>(eventName?: K): void {
__TOKEN_66__ (eventName) {
delete this.listeners[eventName];
} else {
this.listeners = {};
}
}
// Get listener count
listenerCount<K extends EventKey<T>>(eventName: K): number {
return this.listeners[eventName]?.length || 0;
}
}
// Usage examples
interface AppEvents {
'user:login': { userId: string; timestamp: Date };
'user:logout': { userId: string; reason: string };
'data:update': { data: any; version: number };
'error': Error;
'notification': { title: string; message: string; type: 'info' | 'warning' | 'error' };
}
const emitter = new TypedEventEmitter<AppEvents>();
// Type-safe subscription
const unsubscribe = emitter.on('user:login', (data) => {
console.log(`User ${data.userId} logged in at ${data.timestamp}`);
});
// One-time listener
emitter.once('notification', (notification) => {
console.log(`Notification: ${notification.title}`);
});
// Emit with type safety
emitter.emit('user:login', {
userId: '123',
timestamp: new Date()
});
emitter.emit('error', new Error('Something went wrong'));
// Clean up
unsubscribe();
// Advanced usage with async handlers
class AsyncEventEmitter<T extends EventMap> extends TypedEventEmitter<T> {
async emitAsync<K extends EventKey<T>>(eventName: K, data: T[K]): Promise<void> {
const callbacks = this.listeners[eventName];
__TOKEN_87__ (!callbacks) return;
const promises = callbacks.slice().map(callback =>
Promise.resolve().then(() => callback(data))
);
await Promise.all(promises);
}
}