als-event-emitter
Version:
A powerful, asynchronous, promise-based event emitter with support for chaining and advanced event handling.
303 lines (221 loc) • 8.62 kB
Markdown
`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.
- **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:
```js
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
```
```bash
npm install als-event-emitter
```
```js
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
```html
<script src="/node_modules/als-event-emitter/emitter.js"></script>
<script>
const emitter = new EventEmitter();
</script>
```
- Creates a new emitter instance.
- If `chain = true`, `emit` becomes async and returns a `Promise`.
- If `chain = false`, `emit` is synchronous.
- 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.
- Adds a one-time listener for `eventName`.
- Removes all listeners for `eventName`.
- Removes the given `listener` from all events and from the global `anyChain`.
- Removes **all** registered listeners from **all** events (and clears `anyChain` for this instance).
- 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`.
- Registers a listener that will be called for **any** event emitted by the instance.
- One-time version of `onAny`.
- Checks if there are any listeners for `eventName`.
---
- `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`
```js
const emitter = new EventEmitter();
let counter = 0;
// Called for any event
emitter.onAny(() => counter++);
emitter.emit('foo'); // counter = 1
emitter.emit('bar'); // counter = 2
```
```js
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.
```js
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
```
---
- 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.
```js
const emitter = new EventEmitter();
emitter.onAny(() => console.log('A global listener triggered!'));
emitter.emit('doesNotExist'); // Logs: "A global listener triggered!"
```
---
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.
```js
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));
```
---
```js
// 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:
```js
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"
});
```
---
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.
---
```js
const emitter = new EventEmitter();
emitter.on('data', (payload) => {
console.log('Got data:', payload);
});
emitter.emit('data', { key: 'value' });
```
```js
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
```
```js
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'));
```