events-ex
v2.2.0
Published
Browser-friendly enhanced events most compatible with standard node.js, it's powerful eventable ability.
Downloads
870
Maintainers
Readme
events-ex

Browser-friendly enhanced event emitter ability and class. It's modified from event-emitter mainly. It can add/inject the event-able ability to your any class.
Features
- Modular Event-able Ability: Inject event capabilities into any class using
eventable(MyClass)without forced inheritance. - Core Event Enhancements:
- Bubbling & Interruption: Full support for event propagation and mid-stream cancellation.
- Listener Ordering: Precise control via the optional
indexparameter inon()andonce(). - Regex Subscription: Subscribe to multiple events using Regular Expressions.
- Hook-able System: Intercept and modify event behavior at the core level.
- Advanced Asynchronous Features (Specific to
emitAsync):- Configurable Concurrency: Choose between Serial (default) and Parallel execution for async listeners.
- Result Aggregation: Strategies to gather return values:
last(default),first(first success), andcollect(all results). - Fluent API Proxies: Use
.parallel()and.configure()for transient, side-effect-free execution context.
- Architecture: Rewritten core for improved performance and flexibility while maintaining broad compatibility.
- Event Utilities: Built-in support for
pipe,pipeAsync,unify,allOff, andhasListeners.
Differences
Difference with node events
broken change: The event supports bubbling and interruption- the
event objectas listener's "this" object:result: If set, the result is returned to theEvent Emitter.stopped: If set totrue, it prevents the remaining listeners from being executed.target: TheEvent Emitterobject, which was originally thethisobject.type: triggered event type(name).resolved: (Async only) Indicates if a successful result has been found infirstmode.
broken change: Theemitreturn the result of listeners's callback function instead of the successful state.broken change: Thethisobject of listeners' callback function is theEventObject instead of the emitter object.- The emitter object is put into the
targetproperty of theEventObject.
- The emitter object is put into the
- the
- ⚡ Enhanced
emitAsyncMethod (Unique to Async):- Sequential (Serial): Executes listeners one-by-one, respecting
this.stopped. - Concurrent (Parallel): Executes all listeners simultaneously.
- Result Strategies:
last: Returns the result of the final listener (or last to finish).first: Returns the first successful non-undefined result (skips errors).collect: Returns an array of all results in registration order.
- Sequential (Serial): Executes listeners one-by-one, respecting
- Fluent Configuration: Use
.parallel()or.configure({...})for one-time customized async emits. - Listener APIs:
on/once(event: string|RegExp, listener, index?: number)- 📌 Index Parameter (Optional): Allows specifying the insertion position in the listener array.
- 🧪 Regex Event Matching: Listeners can bind to multiple events via regex patterns.
Difference with event-emitter
broken change: The event supports bubbling and interruption (see above).- Adds the
defaultMaxListenersclass property to keep compatibility with node events. - Adds the
setMaxListenersmethod to keep compatibility with node events. - Adds
error,newListenerandremoveListenerevents to keep compatibility with node events. - Adds
listeners()method to keep compatibility with node events. - Adds
listenerCount()class method to keep compatibility with node events.
- Adds async event emitting via
emitAsyncmethod.
🔗 Event Piping & Unification:
pipe(source, target): Sync event forwarding.pipeAsync(source, target, options): Async forwarding with configurable concurrency and aggregation.unify(emitter1, emitter2): Bi-directional synchronization.
Note: The listener throw error should not broke the notification, but it will emit error(emit('error', error, 'notify', eventName, listener, args)) after notification.
Installation
npm install events-exUsage
Extends from EventEmitter class
import {EventEmitter} from 'events-ex';
class MyClass extends EventEmitter {}Add/Inject the event-able ability to your class directly
import {eventable} from 'events-ex';
class MyClass extends MyRoot {}
// inject the eventable ability to MyClass
eventable(MyClass);Core Feature: Listener Ordering (Index Parameter)
const ee = new EventEmitter();
ee.on('test', () => console.log('second'), 1);
ee.on('test', () => console.log('first'), 0); // Insert at index 0
ee.emit('test');
// Output:
// first
// secondCore Feature: Regex Subscription
const ee = new EventEmitter();
ee.on(/^user\..*/, function(data) {
console.log(`Event ${this.type} triggered with`, data);
});
ee.emit('user.login', { id: 1 });
ee.emit('user.logout', { id: 1 });Core Feature: Bubbling & Interruption
import {EventEmitter, states} from 'events-ex';
import {isObject} from 'util-ex';
class MyDb extends EventEmitter {
get(key) {
let result = this.emit('getting', key)
if(isObject(result)) {
if (result.state === states.ABORT) return
if (result.state === states.DONE) return result.result
}
return _get(key)
}
}
let db = new MyDb
db.on('getting', function(key){
let result = myGet(key);
if (result != null) {
this.result = { state: states.DONE, result: result }
this.stopped = true // Skip remaining listeners
} else {
this.result = { state: states.ABORT };
}
})Async-Only Features: Concurrency & Aggregation
These features apply only to the emitAsync method.
const ee = new EventEmitter();
ee.on('task', async () => {
await sleep(100);
return 'result 1';
});
ee.on('task', async () => {
return 'result 2';
});
// 1. Default (Serial): Executes sequentially, returns 'result 2'
const res = await ee.emitAsync('task');
// 2. Parallel + Collect: Executes concurrently, returns ['result 1', 'result 2']
const allResults = await ee.parallel('collect').emitAsync('task');
// 3. Parallel + First: Executes concurrently, returns fastest success ('result 2')
const firstResult = await ee.parallel('first').emitAsync('task');Advanced Features
Async Concurrency Engine (For emitAsync Only)
| Option | Value | Description |
| :--- | :--- | :--- |
| asyncMode | 'serial' | (Default) Listeners run one by one. Supports this.stopped. |
| | 'parallel' | Listeners run concurrently. this.stopped is ignored. |
| resultMode | 'last' | (Default) Returns the result of the last listener (or last to finish). |
| | 'first' | Returns the first non-undefined and successful result. Skips errors. |
| | 'collect' | Returns an array of all results in registration order. |
Proxy Isolation (Fluent API)
Calling .parallel() or .configure() returns a transient Proxy Object (Object.create(this)), allowing thread-safe, isolated configurations for specific emits.
Safe Injection (AoP Compatibility) & Name Collisions
When injecting event capabilities into an existing object or prototype via wrapEventEmitter(target) or eventable(MyClass), a minimal set of methods is injected to minimize the risk of name collisions:
on,once,offemit,emitAsyncsetEmitterOptions
⚠️ Warning on Name Collisions: If your target object already has methods with these names, they will be overwritten.
Solution: Method Renaming
You can use the rename option in eventable to map the emitter methods to custom names on your target:
eventable(MyClass, {
rename: {
emitAsync: 'myEmitAsync',
on: 'addListener'
}
});
// Now use: inst.myEmitAsync('event')Full EventEmitter vs. Minimal Injection
- Standalone: Calling
ee()ornew EventEmitter()without a target returns a full instance containing all advanced methods (including.parallel(),.configure(),.setMaxListeners(), etc.). - Injected: Passing a target to
ee(target)or usingeventableperforms a minimal injection to preserve the target's original footprint. UsesetEmitterOptionson the target to access advanced async configurations.
API
eventable(class[, options]) (events-ex/eventable)
Add the event-able ability to the class directly.
class: the class to be injected the ability.options(object): optional optionsinclude(string[]|string): only these emitter methods will be added to the classexclude(string[]|string): theses emitter methods would not be added to the classmethods(object): hooked methods to the classemitterOptions(object): default options for the emitter (e.g.,asyncMode,resultMode).rename(object): map the emitter methods to custom names on the class.- key: original method name (e.g., 'on', 'emitAsync').
- value: new method name.
hasListeners(obj[, name]) (events-ex/has-listeners)
import {hasListeners, wrapEventEmitter as ee} from 'events-ex';
var emitter = ee();
var listener = function () {};
hasListeners(emitter); // false
emitter.on('foo', listener);
hasListeners(emitter, 'foo'); // truepipeAsync(source, target[, name, options]) (events-ex/pipe-async)
Creates an asynchronous pipeline.
options.asyncMode: Propagation mode ('serial' | 'parallel').options.resultMode: Aggregation strategy.
setEmitterOptions(options)
Configures instance-wide defaults for asyncMode, resultMode, and maxListeners.
