Simple and modern async event emitter
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Helper methods for managing listeners, binding methods, and getting emitter state information.
Remove previously registered event listeners from the emitter.
/**
* Remove one or more event subscriptions
* @param eventName - Single event name or array of event names
* @param listener - The exact listener function that was registered
*/
off<Name extends keyof AllEventData>(
eventName: Name | readonly Name[],
listener: (eventData: AllEventData[Name]) => void | Promise<void>
): void;Usage Examples:
import Emittery from "emittery";
const emitter = new Emittery();
// Save listener reference for later removal
const dataListener = (data) => {
console.log('Data received:', data);
};
// Register listener
emitter.on('data', dataListener);
// Remove specific listener
emitter.off('data', dataListener);
// Remove listener from multiple events
const multiListener = (data) => console.log(data);
emitter.on(['start', 'stop', 'pause'], multiListener);
emitter.off(['start', 'stop', 'pause'], multiListener);
// Anonymous functions cannot be removed
emitter.on('temp', () => console.log('temporary'));
// emitter.off('temp', ???); // No way to reference the anonymous functionRemove all listeners from the emitter or from specific events.
/**
* Clear all event listeners on the instance
* @param eventName - Optional specific event name(s) to clear. If omitted, clears ALL listeners including onAny listeners and async iterators
*/
clearListeners<Name extends keyof EventData>(eventName?: Name | readonly Name[]): void;Usage Examples:
import Emittery from "emittery";
const emitter = new Emittery();
// Add various listeners
emitter.on('data', handleData);
emitter.on('error', handleError);
emitter.onAny(logAllEvents);
// Clear listeners for specific event
emitter.clearListeners('data');
// Clear listeners for multiple specific events
emitter.clearListeners(['data', 'error']);
// Clear ALL listeners (including onAny)
emitter.clearListeners();
// This also clears any active async iterators
const iterator = emitter.events('stream');
emitter.clearListeners('stream'); // Iterator stops receiving eventsGet the number of active listeners for events.
/**
* Get the number of listeners for the specified events or all events
* Counts include: specific event listeners, onAny listeners, and active async iterators
* @param eventName - Optional specific event name(s) to count, or all events if omitted
* @returns Number of active listeners (includes all listener types and async iterators)
*/
listenerCount<Name extends keyof EventData>(eventName?: Name | readonly Name[]): number;Usage Examples:
import Emittery from "emittery";
const emitter = new Emittery();
emitter.on('data', handleData1);
emitter.on('data', handleData2);
emitter.on('error', handleError);
emitter.onAny(logAllEvents);
// Count listeners for specific event
console.log(emitter.listenerCount('data')); // 3 (2 specific + 1 any)
// Count listeners for multiple events
console.log(emitter.listenerCount(['data', 'error'])); // 4
// Count all listeners across all events
console.log(emitter.listenerCount()); // 4 total listeners
// Including async iterators in count
const iterator = emitter.events('stream');
console.log(emitter.listenerCount('stream')); // Includes iterator
await iterator.return(); // Clean upBind Emittery methods to other objects for convenient usage patterns.
/**
* Bind Emittery methods to a target object
* @param target - Object to bind methods to
* @param methodNames - Optional array of method names to bind, defaults to all methods
*/
bindMethods(target: Record<string, unknown>, methodNames?: readonly string[]): void;Usage Examples:
import Emittery from "emittery";
const emitter = new Emittery();
// Bind all methods to an object
const eventTarget = {};
emitter.bindMethods(eventTarget);
// Now you can use methods directly on the target
eventTarget.on('data', handleData);
eventTarget.emit('data', someData);
eventTarget.once('ready').then(() => console.log('Ready!'));
// Bind only specific methods
const logger = {};
emitter.bindMethods(logger, ['on', 'emit']);
logger.on('log', console.log);
logger.emit('log', 'This works!');
// logger.once('test'); // Error: once method not bound
// Useful for API objects
class MyAPI {
constructor() {
this.emitter = new Emittery();
this.emitter.bindMethods(this, ['on', 'off', 'once']);
}
// Now this class can be used directly as an event emitter
async performAction() {
// Internal emit
await this.emitter.emit('action-complete', {result: 'success'});
}
}
const api = new MyAPI();
api.on('action-complete', handleCompletion); // Works directlyimport Emittery from "emittery";
class EventManager {
constructor() {
this.emitter = new Emittery();
this.listeners = new Map();
}
// Managed listener registration
addListener(eventName, listener, id) {
this.emitter.on(eventName, listener);
this.listeners.set(id, {eventName, listener});
}
// Remove by ID
removeListener(id) {
const entry = this.listeners.get(id);
if (entry) {
this.emitter.off(entry.eventName, entry.listener);
this.listeners.delete(id);
}
}
// Get statistics
getStats() {
return {
totalListeners: this.emitter.listenerCount(),
managedListeners: this.listeners.size,
events: Array.from(this.listeners.values())
.map(entry => entry.eventName)
};
}
// Clean up all managed listeners
cleanup() {
for (const [id] of this.listeners) {
this.removeListener(id);
}
}
}import Emittery from "emittery";
const emitter = new Emittery();
// Clean up listeners based on conditions
function cleanupInactiveListeners() {
// This is a conceptual example - actual implementation
// would need to track listener metadata
// Clear listeners that haven't been triggered recently
if (shouldCleanup()) {
emitter.clearListeners('rarely-used-event');
}
// Or clear everything and re-register essential listeners
if (needsReset()) {
emitter.clearListeners();
registerEssentialListeners();
}
}
function registerEssentialListeners() {
emitter.on('error', handleCriticalError);
emitter.on('shutdown', handleShutdown);
}import Emittery from "emittery";
// Pattern 1: Service wrapper
class EventService {
constructor() {
this.emitter = new Emittery();
// Expose only specific methods
this.emitter.bindMethods(this, ['on', 'emit']);
}
// Add service-specific methods
broadcast(message) {
return this.emit('broadcast', message);
}
}
// Pattern 2: Mixin approach
function addEventCapabilities(target) {
const emitter = new Emittery();
emitter.bindMethods(target);
// Add cleanup capability
target.cleanup = () => emitter.clearListeners();
return target;
}
// Pattern 3: Selective binding
class APIClient {
constructor() {
this.emitter = new Emittery();
// Only bind listener methods, not emission
this.emitter.bindMethods(this, ['on', 'off', 'once']);
}
// Keep emission internal
async makeRequest() {
try {
const result = await fetch('/api/data');
await this.emitter.emit('success', result);
} catch (error) {
await this.emitter.emit('error', error);
}
}
}Proper cleanup is important to prevent memory leaks:
import Emittery from "emittery";
const emitter = new Emittery();
// Always clean up when done
function setupTemporaryListeners() {
const listeners = [];
// Track listeners for cleanup
listeners.push(emitter.on('temp1', handler1));
listeners.push(emitter.on('temp2', handler2));
// Clean up later
return () => {
listeners.forEach(unsubscribe => unsubscribe());
};
}
const cleanup = setupTemporaryListeners();
// ... later
cleanup();
// Or clear entire events
emitter.clearListeners(['temp1', 'temp2']);
// Monitor listener count in development
if (process.env.NODE_ENV === 'development') {
setInterval(() => {
const count = emitter.listenerCount();
if (count > 100) {
console.warn(`High listener count: ${count}`);
}
}, 10000);
}Install with Tessl CLI
npx tessl i tessl/npm-emittery