5.3.0 • Published 6 months ago

als-event-emitter v5.3.0

Weekly downloads
-
License
MIT
Repository
-
Last release
6 months ago

als-event-emitter

als-event-emitter is a lightweight, powerful implementation of an event emitter system, designed to be easy to use and extendable. It supports chaining, returns promises for event result handling, and is compatible with both Node.js and browser environments, providing more flexibility compared to the standard Node.js EventEmitter.

What's New

Version 5.3.0

  • Event Name in Context:
    Every listener now receives the event name as part of the context object ({ name, next, resolve, reject, result }). This is useful for identifying the event currently being processed, especially when using onAny.

  • Group Functionality:
    Added support for grouping listeners with the group parameter in on, once, onLast, and onceLast. Listeners assigned to a group will be executed when any event in that group is emitted. This allows for more organized listener management across related events.

    • Note: Group functionality does not apply to global listeners (onAny, onceAny).

    Example:

    const emitter = new EventEmitter();
    
    emitter.on('group1', ({ next }) => {
      console.log('Group listener');
      next();
    });
    
    emitter.on('event1', ({ next }) => {
      console.log('Event 1 listener');
      next();
    }, { group: 'group1' });
    
    emitter.emit('event1');
    // Output:
    // Group listener
    // Event 1 listener

Installation

npm install als-event-emitter

Basic Usage

const EventEmitter = require('als-event-emitter');

// Create an emitter
const emitter = new EventEmitter();

// Register a listener
emitter.on('data', (payload) => {
  console.log('Received data:', payload);
});

// Emit an event
emitter.emit('data', { message: 'Hello World' });

Note: In default mode (new EventEmitter(true)), emit returns a Promise and listeners receive { next, resolve, reject, result }. If you pass false to the constructor (new EventEmitter(true)) emit is synchronous.

Browser usage

<script src="/node_modules/als-event-emitter/emitter.js"></script>
<script>
  const emitter = new EventEmitter();

</script>

API Overview

new EventEmitter(chain = true | false)

  • Creates a new emitter instance.
    • If chain = true, emit becomes async and returns a Promise.
    • If chain = false, emit is synchronous.

emitter.on(eventName, listener, options?)

  • Adds a listener for eventName.
  • If chain = true, listener receives ( { next, resolve, reject, result, name }, ...args ).
  • options can include:
    • once: Executes the listener only once.
    • last: Ensures the listener runs at the end of the chain.
    • group: Links the listener to a group. If another event in the same group is emitted, the group listener is also triggered.

emitter.once(eventName, listener)

  • Adds a one-time listener for eventName.

emitter.off(eventName)

  • Removes all listeners for eventName.

emitter.removeListener(listener)

  • Removes the given listener from all events and from the global anyChain.

emitter.removeAllListeners()

  • Removes all registered listeners from all events (and clears anyChain for this instance).

emitter.emit(eventName, ...args)

  • Emits the event.
  • If chain = true, returns a Promise if at least one of function in chain is async.
  • If chain = false, executes listeners synchronously and returns undefined.

emitter.onAny(listener, { once, last })

  • Registers a listener that will be called for any event emitted by the instance.

emitter.onceAny(listener)

  • One-time version of onAny.

emitter.has(eventName)

  • Checks if there are any listeners for eventName.

Global vs. Instance Listeners

Global (Static) Listeners

  • EventEmitter.on(...), EventEmitter.once(...), etc.
  • These listeners are stored in a global chain (SmartPromise.chain) and are shared by all EventEmitter instances.
  • Use EventEmitter.removeAllListeners() to clear all static/global listeners.

Instance Listeners

  • emitter.on(...), emitter.once(...), etc.
  • Stored in the instance's own map of events.
  • Call emitter.removeAllListeners() to clear the instance's own listeners (including anyChain of that instance).

Advanced Usage

onAny and onceAny

const emitter = new EventEmitter();
let counter = 0;

// Called for any event
emitter.onAny(() => counter++);

emitter.emit('foo'); // counter = 1
emitter.emit('bar'); // counter = 2

onLast, onceLast

emitter.onLast('myEvent', ({ next }) => {
  console.log('Runs last for myEvent');
  next();
});

These functions place the listener at the end of the chain, ensuring it runs after normal listeners.

Grouped Listeners

const emitter = new EventEmitter();

emitter.on('groupA', ({ next }) => {
  console.log('Group A listener');
  next();
});

emitter.on('eventX', ({ next }) => {
  console.log('Event X listener');
  next();
}, { group: 'groupA' });

emitter.emit('eventX');
// Output:
// Group A listener
// Event X listener

Behavior with No Registered Event

  • If you call emit('unknownEvent') and no specific listeners exist for 'unknownEvent', any global listeners (e.g. via onAny, onceAny) will still fire.
  • This is helpful if you want to log or debug every event, even unregistered ones.
const emitter = new EventEmitter();
emitter.onAny(() => console.log('A global listener triggered!'));

emitter.emit('doesNotExist'); // Logs: "A global listener triggered!"

Chaining and Promises

  1. If chain = true:
    Each listener receives:
    • next(result?): Proceed to the next listener in the chain.
    • resolve(value): Resolve the chain immediately.
    • reject(error): Reject the chain immediately.
    • result: The value passed from the previous listener’s next(result).
    • name: The name of the current event being processed.
  2. The final emit(...) call returns a Promise, which resolves or rejects based on the chain’s flow.
const emitter = new EventEmitter(true);

emitter.on('process', ({ next }, data) => {
  console.log('Step 1:', data);
  next('step1-done');
});

emitter.on('process', ({ resolve, result }) => {
  console.log('Step 2:', result);
  resolve('All done');
});

emitter.emit('process', 'start-data')
  .then(finalValue => console.log('Final:', finalValue));

Concurrent and Multi-Emitter Usage

// Multiple Emitters sharing global listeners
EventEmitter.on(() => console.log('Global Listener'));

const emitter1 = new EventEmitter();
const emitter2 = new EventEmitter();

emitter1.emit('someEvent'); // triggers global listener
emitter2.emit('otherEvent'); // triggers global listener

For concurrency in async mode:

const emitter = new EventEmitter(true);

emitter.on('test', async ({ resolve }, val) => {
  // Asynchronous listener
  await new Promise(r => setTimeout(r, 100));
  resolve(`Done-${val}`);
});

// Fire multiple emits concurrently
Promise.all([
  emitter.emit('test', 'X'),
  emitter.emit('test', 'Y')
]).then(([resX, resY]) => {
  console.log(resX, resY); 
  // "Done-X", "Done-Y"
});

Backward Incompatibility

Since version 5.0, emit can return a Promise, and listener signatures have changed to receive ( { next, resolve, reject, result }, ...args ) in chaining mode. If migrating from older versions, ensure your listeners are updated accordingly.


Examples

Basic Usage

const emitter = new EventEmitter();

emitter.on('data', (payload) => {
  console.log('Got data:', payload);
});
emitter.emit('data', { key: 'value' });

Using onceAny

const emitter = new EventEmitter();
let called = 0;
emitter.onceAny(() => {
  called++;
  console.log('Triggered by any event once');
});
emitter.emit('eventA');
emitter.emit('eventB'); // won’t trigger again
console.log('called =', called); // 1

Using onLast

const emitter = new EventEmitter(true);
emitter.on('myEvent', ({ next }) => {
  console.log('First Listener');
  next();
});
emitter.onLast('myEvent', ({ next }) => {
  console.log('Last Listener');
  next();
});
emitter.emit('myEvent').then(() => console.log('All done'));