npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@chaisser/event-emitter

v1.0.1

Published

Simple and lightweight event emitter

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: Hello

Unsubscribing 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 order

Typed 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 logged

Event 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')); // 3

Global 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

  1. Always unsubscribe - Remove listeners when they're no longer needed to prevent memory leaks
  2. Use once - For events that should only fire once (like navigation events)
  3. Handle errors - Event handlers should catch and handle errors gracefully
  4. Use typed events - For complex applications, create typed event emitters for better type safety
  5. Avoid circular dependencies - Don't create circular references in your event handlers
  6. Use async emit - When handlers need to perform async operations, use emitAsync
  7. 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:


🔒 License

MIT - Free to use in personal and commercial projects


👨 Developed by

Doruk Karaboncuk [email protected]


📄 Repository


🤝 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:


Made with ❤️ by @chaisser

npm license downloads typescript