Emittery

repository·main·Indexed 24 days ago

https://github.com/sindresorhus/emittery

A simple, modern, and async-first event emitter for Node.js and the browser. Version 2.0.0 features non-blocking execution by deferring listeners to the next microtask, TypeScript support for strongly typed events, AbortSignal integration for cancellation, and async iteration via for await...of. It includes advanced capabilities such as lifecycle hooks (init/deinit), serial emission via emitSerial(), and automatic cleanup using Symbol.dispose and Symbol.asyncDispose.

Tokens
7.2K
Snippets
20
Records
34
Agent score
83%

What's inside emittery

  1. Overview of Emittery features

    main

    Emittery is an async-first event emitter designed for high performance and modern JavaScript environments. Key features include:

    • Async-first: Listeners are deferred to the next microtask to prevent blocking the main thread.
    • TypeScript support: Provides strongly typed events.
    • Async iteration: Supports for await...of for consuming events.
    • Lifecycle hooks: Supports init and deinit for lazy resource management.
    • Cancellation: Built-in AbortSignal support.
    • Automatic cleanup: Supports Symbol.dispose and Symbol.asyncDispose.
    • Meta events: Allows observing changes to listeners.
    • Debug mode: Includes customizable logging.
    • Zero dependencies: Lightweight and secure.
  2. Understand Emittery's asynchronous scheduling

    main

    Emittery is designed around asynchronous event emission. Understanding these scheduling rules is critical for correct behavior:

    • Asynchronous Execution: Listeners are always deferred to the next microtask. Any synchronous code following an unawaited emit() call will execute before the listeners.
    • Ordering: Listeners are called in the order they were added. "Any" listeners (wildcard listeners) are called after event-specific listeners.
    • Listener Removal: Removing a listener (via .off() or .clearListeners()) prevents it from being invoked, even if the event is currently in the process of being emitted asynchronously.
    • Missed Events: Listeners are not invoked for events emitted before the listener was added.
    • Serial Emission: When using .emitSerial(), a slow listener will delay subsequent listeners. Note that newer events can potentially overtake older ones in this mode.
  3. Enable debugging for Emittery

    main

    You can collect and log debug information using one of the following methods:

    1. Environment Variable: Set the DEBUG environment variable to 'emittery' or '*'.
    2. Global Static Property: Set Emittery.isDebugEnabled = true on the class.
    3. Instance Property: Set myEmitter.debug.enabled = true on a specific instance.
  4. Configure Debugging

    main

    Emittery supports collecting and logging debug information. You can enable debugging in three ways:

    1. Globally: Set the DEBUG environment variable to emittery or *.
    2. Class-wide: Set Emittery.isDebugEnabled = true.
    3. Instance-specific: Pass a debug object in the constructor or set emitter.debug.enabled = true.

    Debug Options

    • name: A string identifier for the instance used in logs.
    • enabled: Boolean to toggle logging for this instance.
    • logger: A custom function (type, debugName, eventName, eventData) => void to handle debug output.
    import Emittery from 'emittery';
    
    // Global enable
    Emittery.isDebugEnabled = true;
    
    // Instance with custom logger
    const emitter = new Emittery({
    	debug: {
    		name: 'myEmitter',
    		enabled: true,
    		logger: (type, debugName, eventName, eventData) => {
    			console.log(`[${type}]: ${eventName}`);
    		}
    	}
    });
    import Emittery from 'emittery';
    
    Emittery.isDebugEnabled = true;
    
    const emitter = new Emittery({debug: {name: 'myEmitter'}});
    
    emitter.on('test', () => {
    	// …
    });
    
    emitter.emit('test');
    	//=> [16:43:20.417][emittery:subscribe][myEmitter] Event Name: test
    	//	data: undefined
  5. Initialize Emittery with typed events

    main

    Emittery is a strictly typed, fully async EventEmitter. To define your events and their associated data, pass an interface to the Emittery constructor where keys are event names and values are the data type passed to listeners. Use undefined for events that do not pass data.

    import Emittery from 'emittery';
    
    const emitter = new Emittery<{
    	open: string,
    	close: undefined
    }>();
    
    // Type-safe emission
    emitter.emit('open', 'foo');
    emitter.emit('close');
    
    // These will cause TypeScript errors:
    emitter.emit('open', 1); // Error: 1 is not a string
    emitter.emit('other');   // Error: 'other' is not in the map
    import Emittery from 'emittery';
    
    const emitter = new Emittery<{
    	open: string,
    	close: undefined
    }>();
    
    // Typechecks just fine because the data type for the `open` event is `string`.
    emitter.emit('open', 'foo\n');
    
    // Typechecks just fine because `close` is present but points to undefined in the event data type map.
    emitter.emit('close');
    
    // TS compilation error because `1` isn't assignable to `string`.
    emitter.emit('open', 1);
    
    // TS compilation error because `other` isn't defined in the event data type map.
    emitter.emit('other');
  6. Initialize Emittery

    main

    Create a new instance of Emittery to manage asynchronous event emission. You can optionally provide a debug configuration object to enable logging.

    import Emittery from 'emittery';
    
    const emittery = new Emittery({ debug: { enabled: true } });
  7. Pass multiple arguments to emit() using destructuring

    main

    Emittery does not support multiple arguments in the .emit() method. To pass multiple pieces of data, wrap them in an array or object and use destructuring in your listener.

    // Use destructuring to handle multiple values passed as an array
    emitter.on('🦄', ({data: [foo, bar]}) => {
    	console.log(foo, bar);
    });
    
    emitter.emit('🦄', [foo, bar]);
  8. Basic usage of Emittery

    main

    To use emittery, import the Emittery class, instantiate it, and use .on() to register listeners and .emit() to trigger events. Listeners are deferred to the next microtask, ensuring non-blocking execution. You can use strings or Symbols as event names.

    import Emittery from 'emittery';
    
    const emitter = new Emittery();
    
    emitter.on('🦄', ({data}) => {
    	console.log(data);
    });
    
    const myUnicorn = Symbol('🦄');
    
    emitter.on(myUnicorn, ({data}) => {
    	console.log(`Unicorns love ${data}`);
    });
    
    emitter.emit('🦄', '🌈'); // Will trigger printing '🌈'
    emitter.emit(myUnicorn, '🦋');  // Will trigger printing 'Unicorns love 🦋'
  9. Subscribe to all events with onAny() and anyEvent()

    main

    If you need to listen to every event emitted by an instance:

    • onAny(listener, options?): Subscribes to a callback that is notified of every event. Returns an unsubscribe method (also a Disposable).
    • anyEvent(options?): Returns an async iterator that buffers every event object emitted. Use await using for automatic cleanup.
    import Emittery from 'emittery';
    
    const emitter = new Emittery();
    
    // Callback approach
    const off = emitter.onAny(({name, data}) => {
    	console.log(name, data);
    });
    
    // Async iterator approach
    for await (const {name, data} of emitter.anyEvent()) {
    	console.log(name, data);
    }
  10. Unsubscribe from events with off()

    main

    Use off(eventName | eventName[], listener) to remove specific event subscriptions. You can pass a single event name or an array of event names.

    import Emittery from 'emittery';
    
    const emitter = new Emittery();
    const listener = ({data}) => console.log(data);
    
    emitter.on(['🦄', '🐶', '🦊'], listener);
    
    emitter.off('🦄', listener);
    emitter.off(['🐶', '🦊'], listener);
  11. Configure Emittery debug mode

    main

    You can globally toggle debug mode for all instances using Emittery.isDebugEnabled. By default, it is true if the DEBUG environment variable is set to emittery or *, otherwise false.

    Individual instances can be configured with specific debug options via the debug property in the constructor:

    • name (string): A name for the instance used in debug output.
    • enabled (boolean): Toggles debug logging for just this instance.
    • logger (Function): A custom function to handle debug data. It receives (type, debugName, eventName, eventData).
    import Emittery from 'emittery';
    
    Emittery.isDebugEnabled = true;
    
    const emitter = new Emittery({
    	debug: {
    		name: 'myEmitter',
    		enabled: true,
    		logger: (type, debugName, eventName, eventData) => {
    			console.log(`[${type}]: ${eventName}`);
    		}
    	}
    });
    
    emitter.on('test', () => {});
    emitter.emit('test');