@chaisser/event-emitter
v1.0.1
Published
Simple and lightweight event emitter
Maintainers
Readme
🔔 @chaisser/event-emitter
Simple and lightweight event emitter for JavaScript and TypeScript
A minimal, fast, and type-safe event emitter that provides pub/sub pattern for your applications. Zero dependencies, fully typed, and easy to use.
✨ Features
- 🔔 Simple API - Easy to use methods for all common event scenarios
- ⚡ Lightweight - Minimal footprint, zero dependencies
- 🎯 Type-safe - Full TypeScript support with generic types
- 🌐 Once support - Subscribe to events that only fire once
- 🔕 Async emit - Emit events asynchronously when needed
- 👂 Global emitter - Shared emitter instance across your application
- 🎪 Typed events - Create typed event emitters with specific event schemas
- 🧩 Error handling - Graceful error handling in event handlers
- 📊 Event tracking - Monitor event counts and listener statistics
📦 Installation
npm install @chaisser/event-emitter
# or
yarn add @chaisser/event-emitter
# or
pnpm add @chaisser/event-emitter🚀 Quick Start
import { EventEmitter, createTypedEventEmitter } from '@chaisser/event-emitter';
// Create a new emitter
const emitter = new EventEmitter<string>();
// Subscribe to events
emitter.on('user:login', (data) => {
console.log('User logged in:', data);
});
// Emit events
emitter.emit('user:login', { userId: 123, username: 'alice' });
// Unsubscribe
emitter.off('user:login', callback);
// Subscribe once (auto-unsubscribe)
emitter.once('user:logout', () => {
console.log('User logged out');
});
// Async emit
emitter.emitAsync('data:received', data);📖 API Reference
EventEmitter Class
| Method | Description | Returns |
|--------|-------------|---------|
| on(event, callback) | Subscribe to event, auto-unsubscribe on error | () => void |
| once(event, callback) | Subscribe once, auto-unsubscribe after first emit | void |
| off(event, callback) | Unsubscribe specific listener from event | void |
| emit(event, data) | Emit event synchronously | void |
| emitAsync(event, data) | Emit event asynchronously | Promise<void> |
| emitAll(events) | Emit multiple events synchronously | void |
| getEvents() | Get all registered events | string[] |
| listenerCount(event) | Get listener count for specific event | number |
| getListeners(event) | Get all listeners for event | Listener[] |
| hasListeners(event) | Check if event has listeners | boolean |
| removeAllListeners(event) | Remove all listeners for event | void |
| removeAllListeners() | Remove all listeners for all events | void |
| clear() | Clear all events and listeners | void |
| getTotalListenerCount() | Get total listener count | number |
| getTotalEventCount() | Get total event count | number |
Typed Event Emitter
| Method | Description |
|--------|-------------|
| createTypedEventEmitter<T>() | Create a typed event emitter with custom event schema |
| TypedEventEmitter<T> | Interface for typed event emitters |
Types
| Type | Description |
|------|-------------|
| EventCallback<T> | Event listener callback type |
| Listener<T> | Event listener with once option |
| TypedEventEmitter<T> | Type-safe event emitter interface |
💡 Usage Examples
Basic Event Subscription
const emitter = new EventEmitter<string>();
const handleLogin = (data) => {
console.log('User logged in:', data);
};
emitter.on('login', handleLogin);
emitter.emit('login', { userId: 123, username: 'alice' });
// Output: User logged in: { userId: 123, username: 'alice' }Multiple Listeners
const emitter = new EventEmitter<string>();
const listener1 = (data) => console.log('Listener 1:', data);
const listener2 = (data) => console.log('Listener 2:', data);
const listener3 = (data) => console.log('Listener 3:', data);
emitter.on('test', listener1);
emitter.on('test', listener2);
emitter.on('test', listener3);
emitter.emit('test', 'Hello');
// Output:
// Listener 1: Hello
// Listener 2: Hello
// Listener 3: HelloUnsubscribing from Events
const emitter = new EventEmitter<string>();
const handleEvent = (data) => console.log('Received:', data);
const unsubscribe = emitter.on('test', handleEvent);
// Emit event
emitter.emit('test', 'First');
// Unsubscribe
unsubscribe();
// Emit again
emitter.emit('test', 'Second');
// Output: Second (listener1 removed)Subscribe Once
const emitter = new EventEmitter<string>();
const handleFirstEmit = (data) => console.log('First emit:', data);
emitter.once('data:received', handleFirstEmit);
emitter.emit('data:received', 'First');
emitter.emit('data:received', 'Second');
emitter.emit('data:received', 'Third');
// Output:
// First emit: First
// (handler automatically unsubscribed after first emit)Async Event Handling
const emitter = new EventEmitter<string>();
const handleData = async (data) => {
console.log('Received data:', data);
// Simulate async processing
await new Promise(resolve => setTimeout(resolve, 100));
console.log('Processed:', data);
};
emitter.on('data', handleData);
// Synchronous emit
emitter.emit('data', 'Sync data');
// Asynchronous emit
await emitter.emitAsync('data', 'Async data');Multiple Events
const emitter = new EventEmitter<string>();
emitter.emitAll([
{ event: 'start', data: 'App started' },
{ event: 'init', data: 'Initialized' },
{ event: 'ready', data: 'Ready to serve' }
]);
// All events emitted in orderTyped Events
interface AppEvents {
'user:login': { userId: number; username: string };
'user:logout': { userId: number };
'notification:new': { type: string; message: string };
}
const emitter = createTypedEventEmitter<AppEvents>();
emitter.on('user:login', (data) => {
console.log('User logged in:', data.userId, data.username);
});
emitter.emit('user:login', { userId: 123, username: 'alice' });
// Type-safe event handling
emitter.on('notification:new', (data) => {
console.log(`New ${data.type} notification:`, data.message);
});Error Handling
const emitter = new EventEmitter<string>();
const errorHandler = (data) => {
throw new Error('Processing error');
};
emitter.on('process', errorHandler);
try {
emitter.emit('process', 'data');
} catch (error) {
console.error('Handler error:', error.message);
}
// Error will be caught and loggedEvent Statistics
const emitter = new EventEmitter<string>();
emitter.on('test', () => {});
emitter.on('test', () => {});
emitter.on('test', () => {});
console.log('Total listeners:', emitter.getTotalListenerCount()); // 3
console.log('Total events:', emitter.getTotalEventCount()); // 1
console.log('test event listeners:', emitter.listenerCount('test')); // 3Global Emitter
import { getGlobalEmitter } from '@chaisser/event-emitter';
// Get global emitter instance
const globalEmitter = getGlobalEmitter();
// Share emitter across modules
globalEmitter.on('shared', () => console.log('Shared handler'));🎯 Advanced Examples
React Integration
import { useEffect, useRef } from 'react';
import { EventEmitter } from '@chaisser/event-emitter';
function useEvent<T>(emitter: EventEmitter<T>, event: string, callback: (data: T) => void) {
const callbackRef = useRef(callback);
useEffect(() => {
const unsubscribe = emitter.on(event, (data: T) => callbackRef.current(data));
return () => unsubscribe();
}, [emitter, event]);
}
// Usage in component
function DataComponent() {
const emitter = useRef(new EventEmitter<string>());
useEvent(emitter.current, 'data:received', (data) => {
console.log('Received data:', data);
});
const handleData = () => {
emitter.current.emit('data:received', JSON.stringify({ timestamp: Date.now() }));
};
return <button onClick={handleData}>Send Data</button>;
}Event Bus Pattern
import { EventEmitter } from '@chaisser/event-emitter';
class EventBus {
constructor() {
this.emitters = new Map<string, EventEmitter>();
}
getEmitter(channel: string) {
if (!this.emitters.has(channel)) {
this.emitters.set(channel, new EventEmitter());
}
return this.emitters.get(channel)!;
}
publish(channel: string, event: string, data: any) {
this.getEmitter(channel).emit(event, data);
}
subscribe(channel: string, event: string, callback: (data: any) => void) {
return this.getEmitter(channel).on(event, callback);
}
}
const eventBus = new EventBus();
// Publish data to specific channel
eventBus.publish('user', 'update', { userId: 123 });Event Aggregation
import { EventEmitter } from '@chaisser/event-emitter';
class EventAggregator {
private emitter = new EventEmitter();
private state = {};
constructor() {
this.emitter.on('update', this.handleUpdate.bind(this));
}
handleUpdate(data) {
this.state = { ...this.state, ...data };
}
emit(data) {
this.emitter.emit('update', data);
}
getState() {
return this.state;
}
getEmitter() {
return this.emitter;
}
}
const aggregator = new EventAggregator();
aggregator.emit({ a: 1, b: 2 });
aggregator.emit({ c: 3 });
console.log(aggregator.getState()); // { a: 1, b: 2, c: 3 }Event Filtering
import { EventEmitter } from '@chaisser/event-emitter';
class FilterableEmitter {
private emitter = new EventEmitter();
onFiltered(event: string, predicate: (data: any) => boolean, callback: (data: any) => void) {
this.emitter.on(event, (data) => {
if (predicate(data)) {
callback(data);
}
});
emit(event: string, data: any) {
this.emitter.emit(event, data);
}
getEmitter() {
return this.emitter;
}
}
const filterableEmitter = new FilterableEmitter();
// Only handle valid user updates
filterableEmitter.onFiltered('user:update', (user) => user.age >= 18, (user) => {
console.log('Adult user updated:', user);
});
filterableEmitter.emit('user:update', { userId: 1, age: 20 }); // Handled
filterableEmitter.emit('user:update', { userId: 2, age: 15 }); // Not handled🔧 Best Practices
- Always unsubscribe - Remove listeners when they're no longer needed to prevent memory leaks
- Use once - For events that should only fire once (like navigation events)
- Handle errors - Event handlers should catch and handle errors gracefully
- Use typed events - For complex applications, create typed event emitters for better type safety
- Avoid circular dependencies - Don't create circular references in your event handlers
- Use async emit - When handlers need to perform async operations, use
emitAsync - Name your events - Use descriptive event names following a convention like
module:action
🏃 Performance
- ⚡ Fast - O(1) subscription and emission
- 🧩 Memory efficient - Automatic cleanup of once listeners
- 🎯 Optimized for common cases - Special handling for frequently used patterns
- 📊 No tracking overhead - Minimal overhead for simple pub/sub
📄 Types
// Event listener callback
export type EventCallback<T> = (data: T) => void;
// Event listener with once option
export type Listener<T> = {
callback: EventCallback<T>;
once?: boolean;
};
// Event emitter interface
export interface IEventEmitter<T = Record<string, unknown> = void> {
on(event: string, callback: EventCallback<T>): () => void;
once(event: string, callback: EventCallback<T>): void;
off(event: string, callback: EventCallback<T>): void;
emit(event: string, data: T): void;
emitAsync(event: string, data: T): Promise<void>;
emitAll(events: Array<{ event: string; data: T }>): void;
getEvents(): string[];
listenerCount(event: string): number;
getListeners(event: string): Listener<T>[];
hasListeners(event: string): boolean;
removeAllListeners(event: string): void;
removeAllListeners(): void;
clear(): void;
getTotalListenerCount(): number;
getTotalEventCount(): number;
}
// Typed event emitter interface
export interface TypedEventEmitter<T extends Record<string, unknown>> {
on<K extends keyof T>(event: K, callback: (data: T[K]) => void): () => void;
once<K extends keyof T>(event: K, callback: (data: T[K]) => void): void;
off<K extends keyof T>(event: K, callback: (data: T[K]) => void): void;
emit<K extends keyof T>(event: K, data: T[K]): void;
emitAsync<K extends keyof T>(event: K, data: T[K]): Promise<void>;
}
// Event emitter class
export class EventEmitter<T = Record<string, unknown> = void> {
// ... implementation
}🔗 Related Packages
Explore our other utility packages in the @chaisser namespace:
- @chaisser/event-emitter (this package) - Typed event emitter
- @chaisser/string-wizard - Advanced string manipulation
- @chaisser/type-guard - Runtime type guards and validators
- @chaisser/uuid-v7 - Time-ordered UUID v7 generator
- @chaisser/wait-for - Promise-based wait utilities
- @chaisser/regex-humanizer - Regex to human-readable descriptions
- @chaisser/password-strength - Password strength checker
- @chaisser/human-time - Human-readable time formatting
- @chaisser/obj-path - Safe dot-notation object access
- @chaisser/debounce-throttle - Rate limiting utilities
- @chaisser/color-utils - Color conversion utilities
- @chaisser/deep-clone - Deep cloning functions
- @chaisser/array-group-by - Array grouping utilities
- @chaisser/merge-objects - Object merge utilities
- @chaisser/chunk-array - Array chunking functions
🔒 License
MIT - Free to use in personal and commercial projects
👨 Developed by
Doruk Karaboncuk [email protected]
📄 Repository
- GitHub: @chaisser
- NPM: @chaisser/event-emitter
🤝 Contributing
Contributions are welcome! Feel free to:
- Report bugs
- Suggest new features
- Submit pull requests
- Improve documentation
📞 Support
For issues, questions, or suggestions, please reach out through:
- Email: [email protected]
- GitHub Issues: Create an issue
Made with ❤️ by @chaisser
