event-emitter-enhancer
Version:
Enhances the Node.js EventEmitter with extra capabilities.
1,089 lines (978 loc) • 32.1 kB
JavaScript
'use strict';
const funcs = require('funcs-js');
const later = require('node-later');
const defer = later();
/**
* Extends the Node.js events.EventEmitter with extra capabilities.
*
* @author Sagie Gur-Ari
* @namespace EventEmitterEnhancer
*/
/**
* 'filter' callback.
*
* @callback FilterCallback
* @param {String} type - The event type
* @param {*} [params] - The event parameters
* @returns {Boolean} True to continue with the emit, false to prevent emit
*/
/**
* 'else' callback.
*
* @callback ElseCallback
* @param {String} type - The event type
* @param {*} [params] - The event parameters
*/
/**
* 'async-emit' callback.
*
* @callback AsyncEmitCallback
* @param {String} type - The event type
* @param {*} [params] - The event parameters
* @param {Boolean} emitted - True if emitted, else false
*/
/*jslint debug: true */
/*istanbul ignore next*/
/**
* This class holds all the extended capabilities added to any emitter.
*
* @author Sagie Gur-Ari
* @class EnhancedEventEmitter
* @public
*/
function EnhancedEventEmitter() {
//should not be called
}
/*jslint debug: false */
/**
* Marker attribute to prevent multiple wrapping of emitter.
*
* @member {Boolean}
* @alias EnhancedEventEmitter.enhancedEmitterType
* @memberof! EnhancedEventEmitter
* @private
*/
EnhancedEventEmitter.prototype.enhancedEmitterType = true;
/**
* If true, all events will not trigger any listener (or 'else' listener).<br>
* The emit function will simply do nothing.
*
* @member {Boolean}
* @alias EnhancedEventEmitter.suspended
* @memberof! EnhancedEventEmitter
* @public
*/
EnhancedEventEmitter.prototype.suspended = false;
/**
* If defined, events will be splitted by this separator and emitted as partials.<br>
* For example, if the separator is ':' an event event1:event2:event3 will be emitted as 3 events: event1, event1:event2, event1:event2:event3.
*
* @member {String}
* @alias EnhancedEventEmitter.subscriptionSeparator
* @memberof! EnhancedEventEmitter
* @public
*/
EnhancedEventEmitter.prototype.subscriptionSeparator = null;
/*eslint-disable valid-jsdoc*/
//jscs:disable jsDoc
/**
* See node.js events.EventEmitter.on.<br>
* This function also returns a removeListener function to easily remove the provided listener.
*
* @function
* @memberof! EnhancedEventEmitter
* @public
* @param {String} event - The name of the event
* @param {function} listener - The callback function
* @returns {function} The remove listener function
* @example
* ```js
* const EnhancedEventEmitter = EventEmitterEnhancer.extend(EventEmitter);
* const emitter = new EnhancedEventEmitter();
*
* const remove = emitter.on('error', function (error) {
* console.error(error);
* });
*
* //remove listener (no longer need to keep a reference to the listener function)
* remove();
* ```
*
* @also
*
* Enables more complex on capabilities including providing multiple listeners/event names, timeout the listener and more.<br>
* To remove the listener/s, the returned function must be called instead of doing emitter.removeListener(...)
*
* @function
* @memberof! EnhancedEventEmitter
* @public
* @param {Object} options - All options needed to setup the listeners
* @param {String[]|String} options.event - The event name or an array of event names
* @param {function[]|function} options.listener - The callback function or an array of callback functions
* @param {Boolean} [options.async=false] - If true, the callback functions will be called after next tick
* @param {Number} [options.timeout] - If provided, the returned remove listener function will be called after the provided timeout value in millies (unless called manually before the timeout was triggered)
* @returns {function} The remove listener function
* @example
* ```js
* const EnhancedEventEmitter = EventEmitterEnhancer.extend(EventEmitter);
* const emitter = new EnhancedEventEmitter();
*
* const removeListener = emitter.on({
* event: ['error', 'connection-error', 'write-error', 'read-error'], //The event names (can be string for a single event)
* listener: [ //The listener callback functions (can be a function instead of an array for a single listener callback)
* function firstListener(arg1, arg2) {
* //do something
* },
* function secondListener(arg1, arg2) {
* //do something
* }
* ],
* async: true, //The callback functions will be called after next tick
* timeout: 1500 //All listeners will be removed after the provided timeout (if not provided, listeners can only be removed manually via returned function)
* });
*
* //emit any event
* emitter.emit('write-error', 1, 2, 3);
*
* //once done, remove all listeners from all events
* removeListener();
* ```
*/
EnhancedEventEmitter.prototype.on = function (eventOrOptions, listener) {
const self = this;
let removeListener;
if (eventOrOptions && (!listener) && (typeof eventOrOptions === 'object') && eventOrOptions.listener && eventOrOptions.event) {
let listeners = eventOrOptions.listener;
if (typeof listeners === 'function') {
listeners = [
listeners
];
}
const events = eventOrOptions.event;
const asyncOn = eventOrOptions.async;
const timeout = eventOrOptions.timeout || 0;
let timeoutID;
const getListener = function (eventListener) {
let onEvent = eventListener;
if (asyncOn) {
onEvent = self.asAsyncListener(eventListener);
}
return onEvent;
};
let removeFunction;
const removeFunctions = [];
for (let index = 0; index < listeners.length; index++) {
listener = listeners[index];
listener = getListener(listener);
removeFunction = self.onAny(events, listener);
removeFunctions.push(removeFunction);
}
removeListener = function () {
for (let funcIndex = 0; funcIndex < removeFunctions.length; funcIndex++) {
removeFunctions[funcIndex]();
}
if (timeoutID) {
clearTimeout(timeoutID);
timeoutID = null;
}
};
if (timeout && (timeout > 0)) {
timeoutID = setTimeout(removeListener, timeout);
}
} else {
this.baseOn(eventOrOptions, listener);
removeListener = function () {
self.removeListener(eventOrOptions, listener);
};
}
removeListener = funcs.once(removeListener);
return removeListener;
};
//jscs:enable jsDoc
/*eslint-enable valid-jsdoc*/
/**
* See node.js events.EventEmitter.once.<br>
* This function also returns a removeListener function to easily remove the provided listener.
*
* @function
* @memberof! EnhancedEventEmitter
* @public
* @param {String} event - The name of the event
* @param {function} listener - The callback function
* @returns {function} The remove listener function
* @example
* ```js
* const EnhancedEventEmitter = EventEmitterEnhancer.extend(EventEmitter);
* const emitter = new EnhancedEventEmitter();
*
* const remove = emitter.once('error', function (error) {
* console.error(error);
* });
*
* //remove listener (no longer need to keep a reference to the listener function)
* remove();
* ```
*/
EnhancedEventEmitter.prototype.once = function (event, listener) {
const self = this;
this.baseOnce(event, listener);
return funcs.once(function removeListener() {
self.removeListener(event, listener);
});
};
/**
* See node.js events.EventEmitter.removeAllListeners.<br>
* This function is modified to also accept an array of event names.
*
* @function
* @memberof! EnhancedEventEmitter
* @public
* @param {String|Array} [event] - The name/s of the event
* @example
* ```js
* const EnhancedEventEmitter = EventEmitterEnhancer.extend(EventEmitter);
* const emitter = new EnhancedEventEmitter();
*
* //same as the basic removeAllListeners
* emitter.removeAllListeners('my-event');
*
* //also supports array of event names
* emitter.removeAllListeners(['my-event', 'another-event']);
* ```
*/
EnhancedEventEmitter.prototype.removeAllListeners = function (event) {
if (event) {
if (Array.isArray(event)) {
for (let index = 0; index < event.length; index++) {
this.removeAllListeners(event[index]);
}
} else {
this.baseRemoveAllListeners(event);
}
}
};
/**
* Suspends all emit calls for the provided event name (including 'else' listeners).<br>
* For suspended events, the emit function will simply do nothing ('else' listeners won't be invoked either).
*
* @function
* @memberof! EnhancedEventEmitter
* @public
* @param {String} event - The event to suspend
* @example
* ```js
* const EnhancedEventEmitter = EventEmitterEnhancer.extend(EventEmitter);
* const emitter = new EnhancedEventEmitter();
* emitter.on('test', function () {
* //will never be called
* });
*
* emitter.suspended = true; //suspend ALL events (to unsuspend use emitter.suspended = false;)
* //or
* emitter.suspend('test'); //suspend only 'test' event (to unsuspend use emitter.unsuspend('test');)
*
* emitter.emit('test');
* ```
*/
EnhancedEventEmitter.prototype.suspend = function (event) {
this.suspendedEvents = this.markEvent(event, this.suspendedEvents);
};
/**
* Unsuspends the emit calls for the provided event name.
*
* @function
* @memberof! EnhancedEventEmitter
* @public
* @param {String} event - The event to unsuspend
*/
EnhancedEventEmitter.prototype.unsuspend = function (event) {
if (event && this.suspendedEvents) {
delete this.suspendedEvents[event];
}
};
/**
* Adds an 'else' listener which will be triggered by all events that do not have a listener currently for them (apart of the special 'error' event).
*
* @function
* @memberof! EnhancedEventEmitter
* @public
* @param {ElseCallback} listener - The listener that will catch all 'else' events
* @example
* ```js
* const EnhancedEventEmitter = EventEmitterEnhancer.extend(EventEmitter);
* const emitter = new EnhancedEventEmitter();
* emitter.else(function onUnhandledEvent(event, arg1, arg2) {
* //logic here....
*
* //to remove 'else' listeners, simply use the unelse function
* emitter.unelse(this);
* });
*
* emitter.emit('test', 1, 2);
* ```
*/
EnhancedEventEmitter.prototype.else = function (listener) {
this.elseListeners = this.elseListeners || [];
this.elseListeners.push(listener);
};
/**
* Removes the provided 'else' listener.<br>
* Same as 'unelse' function.
*
* @function
* @memberof! EnhancedEventEmitter
* @public
* @param {ElseCallback} listener - The listener to remove
*/
EnhancedEventEmitter.prototype.removeElseListener = function (listener) {
if (listener && this.elseListeners && this.elseListeners.length) {
let index;
do {
index = this.elseListeners.indexOf(listener);
if (index !== -1) {
this.elseListeners.splice(index, 1);
}
} while (index !== -1);
}
};
/**
* See 'removeElseListener' documentation.
*
* @function
* @memberof! EnhancedEventEmitter
* @public
* @param {ElseCallback} listener - The listener to remove
*/
EnhancedEventEmitter.prototype.unelse = EnhancedEventEmitter.prototype.removeElseListener; //shorter function name
/**
* Removes all 'else' listeners.
*
* @function
* @memberof! EnhancedEventEmitter
* @public
*/
EnhancedEventEmitter.prototype.removeAllElseListeners = function () {
if (this.elseListeners && this.elseListeners.length) {
this.elseListeners.splice(0, this.elseListeners.length);
}
};
/**
* In case an event with the provided name is emitted but no listener is attached to it, an error event will emitted by this emitter instance instead.
*
* @function
* @memberof! EnhancedEventEmitter
* @public
* @param {String} event - The event name
* @example
* ```js
* const EnhancedEventEmitter = EventEmitterEnhancer.extend(EventEmitter);
* const emitter = new EnhancedEventEmitter();
* emitter.on('error', function (error) {
* //logic here...
*
* //To remove elseError
* emitter.unelseError('test');
* });
*
* emitter.elseError('test');
*
* emitter.emit('test');
* ```
*/
EnhancedEventEmitter.prototype.elseError = function (event) {
this.elseErrorEvents = this.markEvent(event, this.elseErrorEvents);
};
/**
* Removes the else-error handler for the provided event.<br>
* Same as 'unelseError' function.
*
* @function
* @memberof! EnhancedEventEmitter
* @public
* @param {String} event - The event name
*/
EnhancedEventEmitter.prototype.removeElseError = function (event) {
if (event && this.elseErrorEvents) {
delete this.elseErrorEvents[event];
}
};
/**
* See 'removeElseError' documentation.
*
* @function
* @memberof! EnhancedEventEmitter
* @public
* @param {String} event - The event name
*/
EnhancedEventEmitter.prototype.unelseError = EnhancedEventEmitter.prototype.removeElseError; //shorter function name
/**
* See Node.js events.EventEmitter documentation.
*
* @function
* @memberof! EnhancedEventEmitter
* @public
* @param {String} event - The event name
* @param {*} [params] - The event parameters
* @returns {Boolean} True if a listener or an 'else' listener handled the event
*/
EnhancedEventEmitter.prototype.emit = function () {
if (this.subscriptionSeparator) {
return this.doEmitByPath.apply(this, arguments);
}
return this.doEmit.apply(this, arguments);
};
/**
* Runs the emit flow for all event paths splitted by the subscriptionSeparator value.
*
* @function
* @memberof! EnhancedEventEmitter
* @private
* @param {String} event - The event name
* @param {*} [params] - The event parameters
* @returns {Boolean} True if a listener or an 'else' listener handled the event
*/
EnhancedEventEmitter.prototype.doEmitByPath = function (event) {
if (!this.subscriptionSeparator) {
return false;
}
const events = event.split(this.subscriptionSeparator);
let handled = false;
let eventName;
for (let index = 0; index < events.length; index++) {
const argumentsArray = Array.prototype.slice.call(arguments, 0);
if (eventName) {
eventName = eventName + this.subscriptionSeparator + events[index];
} else {
eventName = events[index];
}
argumentsArray[0] = eventName;
handled = this.doEmit.apply(this, argumentsArray) || handled;
}
return handled;
};
/**
* Runs the emit flow only for the given event.
*
* @function
* @memberof! EnhancedEventEmitter
* @private
* @param {String} event - The event name
* @param {*} [params] - The event parameters
* @returns {Boolean} True if a listener or an 'else' listener handled the event
*/
EnhancedEventEmitter.prototype.doEmit = function (event) {
let emitted = false;
if ((!this.suspended) && ((!this.suspendedEvents) || (!this.suspendedEvents[event]))) {
const emitArguments = arguments;
if (this.runFilterChain(emitArguments)) {
emitted = this.baseEmit.apply(this, emitArguments);
if (!emitted) {
emitted = this.handleNoEmit(event, emitArguments);
}
}
}
return emitted;
};
/**
* Handles events which had no listeners.
*
* @function
* @memberof! EnhancedEventEmitter
* @private
* @param {String} event - The event name
* @param {Array} eventArguments - All the arguments to send the else callbacks
* @returns {Boolean} True if a listener or an 'else' listener handled the event
*/
EnhancedEventEmitter.prototype.handleNoEmit = function (event, eventArguments) {
let emitted = false;
const elseErrorDefined = (this.elseErrorEvents && this.elseErrorEvents[event]);
if ((this.elseListeners && this.elseListeners.length) || elseErrorDefined) {
if (elseErrorDefined) {
this.emit('error', new Error('No listener attached for event: ' + event));
} else {
this.invokeElseListener(eventArguments);
emitted = true;
}
}
return emitted;
};
/**
* Invokes all of the 'else' listeners.
*
* @function
* @memberof! EnhancedEventEmitter
* @private
* @param {Array} eventArguments - All the arguments to send the else callbacks
*/
EnhancedEventEmitter.prototype.invokeElseListener = function (eventArguments) {
for (let index = 0; index < this.elseListeners.length; index++) {
this.elseListeners[index].apply(this.elseListeners[index], eventArguments);
}
};
/**
* Invokes the emit after a timeout to enable calling flow to continue and not block due to event listeners.
*
* @function
* @memberof! EnhancedEventEmitter
* @public
* @param {String} event - The event name
* @param {*} [params] - The event parameters (if last param is a function, it is considered as the callback of the emitAsync)
* @param {AsyncEmitCallback} [callback] - The async callback
* @example
* ```js
* const EnhancedEventEmitter = EventEmitterEnhancer.extend(EventEmitter);
* const emitter = new EnhancedEventEmitter();
* emitter.on('test', function onTestEvent(num1, num2) {
* //event logic here
* });
*
* emitter.emitAsync('test', 1, 2, function onEmitDone(event, num1, num2, emitted) {
* //emit callback logic
* });
* ```
*/
EnhancedEventEmitter.prototype.emitAsync = function (event) {
if ((!event) || (typeof event !== 'string')) {
throw new Error('Event not provided.');
}
const self = this;
let callback;
const argumentsArray = Array.prototype.splice.call(arguments, 0);
if ((argumentsArray.length > 1) && (typeof argumentsArray[argumentsArray.length - 1] === 'function')) {
callback = argumentsArray.pop();
}
defer(function invokeEmit() {
const emitted = self.emit.apply(self, argumentsArray);
if (callback) {
argumentsArray.push(emitted);
callback.apply(callback, argumentsArray);
}
});
};
/**
* Adds a listener that will be triggered after a timeout during an emit.<br>
* This ensures that the provided listener is invoked after all other listeners and that it will not block the emit caller flow.<br>
* To remove the listener, the returned function must be called instead of doing emitter.removeListener(...)
*
* @function
* @memberof! EnhancedEventEmitter
* @public
* @param {String} event - The event name
* @param {function} listener - The listener function
* @returns {function} The remove listener function
* @example
* ```js
* const EnhancedEventEmitter = EventEmitterEnhancer.extend(EventEmitter);
* const emitter = new EnhancedEventEmitter();
* emitter.on('test', function onEventSync() {
* //sync handle function logic
* });
* const removeListener = emitter.onAsync('test', function onEventAsync() {
* //async handle function logic
* });
*
* emitter.emit('test', 1, 2);
*
* //remove the async listener
* removeListener();
* ```
*/
EnhancedEventEmitter.prototype.onAsync = function (event, listener) {
let removeAsyncListener = null;
if (event && listener && (typeof listener === 'function')) {
const self = this;
const onEvent = self.asAsyncListener(listener);
self.on(event, onEvent);
removeAsyncListener = funcs.once(function removeListener() {
self.removeListener(event, onEvent);
});
} else {
throw new Error('Missing mandatory parameters');
}
return removeAsyncListener;
};
/**
* Adds a listener to all provided event names.<br>
* To remove the listener, the returned function must be called instead of doing emitter.removeListener(...)
*
* @function
* @memberof! EnhancedEventEmitter
* @public
* @param {String|Array} events - The event name/s
* @param {function} listener - The listener function
* @returns {function} The remove listener function
* @example
* ```js
* const EnhancedEventEmitter = EventEmitterEnhancer.extend(EventEmitter);
* const emitter = new EnhancedEventEmitter();
*
* //add same listener to multiple events
* const remove = emitter.onAny(['test1', 'test2', 'test3'], function (arg1, arg2, arg3) {
* console.log(arg1, arg2, arg3);
* });
*
* //remove listener from all events
* remove();
* ```
*/
EnhancedEventEmitter.prototype.onAny = function (events, listener) {
const self = this;
let removeListener = null;
if (events && listener && (typeof listener === 'function')) {
if (Array.isArray(events)) {
for (let index = 0; index < events.length; index++) {
self.on(events[index], listener);
}
removeListener = function () {
for (let index = 0; index < events.length; index++) {
self.removeListener(events[index], listener);
}
};
} else {
self.on(events, listener);
removeListener = function () {
self.removeListener(events, listener);
};
}
} else {
throw new Error('Missing mandatory parameters');
}
removeListener = funcs.once(removeListener);
return removeListener;
};
/**
* Adds a filter that will be triggered before every emit for the provided event type (if no event is provided, than the filter is invoked for all events).<br>
* The filter enables to prevent events from reaching the listeners in case some criteria is met.
*
* @function
* @memberof! EnhancedEventEmitter
* @public
* @param {String} [event] - The event name. If not provided, the filter is relevant for all events.
* @param {FilterCallback} filter - The filter function
* @returns {function} The remove filter function
* @example
* ```js
* const EnhancedEventEmitter = EventEmitterEnhancer.extend(EventEmitter);
* const emitter = new EnhancedEventEmitter();
*
* //add filters for test event only
* const removeTestEventFilter = emitter.filter('test', function (event, arg1, arg2) {
* if (arg1 && (arg1 > 3)) {
* return true; //continue with emit
* }
*
* return false; //prevent emit
* });
* emitter.filter('test', function (event, arg1, arg2) {
* if (arg2 && (arg2 < 20)) {
* return true; //continue with emit
* }
*
* return false; //prevent emit
* });
*
* //add global filter for all events
* emitter.filter(function (event, arg1, arg2) {
* if (arg1 && (arg1 > 5)) {
* return true; //continue with emit
* }
*
* return false; //prevent emit
* });
* const removeGlobalArg2Filter = emitter.filter(function (event, arg1, arg2) {
* if (arg2 && (arg2 < 18)) {
* return true; //continue with emit
* }
*
* return false; //prevent emit
* });
*
* emitter.on('test', function onTestEvent(arg1, arg2) {
* //event logic here...
* });
*
* emitter.emit('test', 10, 15);
*
* //remove some filters
* removeTestEventFilter();
* removeGlobalArg2Filter();
* ```
*/
EnhancedEventEmitter.prototype.addFilter = function (event, filter) {
const self = this;
let removeFunction = null;
if ((!filter) && event && (typeof event === 'function')) {
filter = event;
event = null;
}
if (!filter) {
throw new Error('Filter not provided.');
}
if (typeof filter !== 'function') {
throw new Error('Filter is not a function.');
}
self.filters = self.filters || {};
if (event) {
removeFunction = self.addEventFilter(event, filter);
} else {
removeFunction = self.addGlobalFilter(filter);
}
return removeFunction;
};
/**
* Adds an event filter (See addFilter)
*
* @function
* @memberof! EnhancedEventEmitter
* @public
* @param {String} event - The event name.
* @param {FilterCallback} filter - The filter function
* @returns {function} The remove filter function
*/
EnhancedEventEmitter.prototype.addEventFilter = function (event, filter) {
const self = this;
self.filters.event = self.filters.event || {};
self.filters.event[event] = self.filters.event[event] || [];
self.filters.event[event].push(filter);
return funcs.once(function removeEventFilter() {
if (self.filters && self.filters.event && self.filters.event[event] && self.filters.event[event].length) {
const index = self.filters.event[event].indexOf(filter);
if (index !== -1) {
self.filters.event[event].splice(index, 1);
}
}
});
};
/**
* Adds a global filter (See addFilter)
*
* @function
* @memberof! EnhancedEventEmitter
* @public
* @param {FilterCallback} filter - The filter function
* @returns {function} The remove filter function
*/
EnhancedEventEmitter.prototype.addGlobalFilter = function (filter) {
const self = this;
self.filters.global = self.filters.global || [];
self.filters.global.push(filter);
return funcs.once(function removeGlobalFilter() {
if (self.filters && self.filters.global && self.filters.global.length) {
const index = self.filters.global.indexOf(filter);
if (index !== -1) {
self.filters.global.splice(index, 1);
}
}
});
};
/**
* See 'addFilter' documentation.
*
* @function
* @memberof! EnhancedEventEmitter
* @public
* @param {String} [event] - The event name. If not provided, the filter is relevant for all events.
* @param {FilterCallback} filter - The filter function
* @returns {function} The remove filter function
*/
EnhancedEventEmitter.prototype.filter = EnhancedEventEmitter.prototype.addFilter; //shorter function name
/**
* Will setup an event proxy so if any of the requested event/s are fired from the provided emitter/s, they will be triggered by this emitter.
*
* @function
* @memberof! EnhancedEventEmitter
* @public
* @param {Object[]|Object} emitters - An event emitter or array of event emitters to proxy the events from
* @param {String[]|String} events - A single event name or an array of event names to proxy from the provided emitter/s
* @returns {function} Once invoked, will stop proxying of events
* @example
* ```js
* const EnhancedEventEmitter = EventEmitterEnhancer.extend(EventEmitter);
* const emitter = new EnhancedEventEmitter();
*
* //proxy the 'data' and 'end' events from all sockets
* const stop = emitter.proxyEvents(sockets, ['data', 'end']);
*
* //listen to events via emitter
* emitter.on('data', onData);
*
* //stop events proxy
* stop();
* ```
*/
EnhancedEventEmitter.prototype.proxyEvents = function (emitters, events) {
if (!emitters) {
throw new Error('Event Emitter/s not provided.');
}
if (!events) {
throw new Error('Event/s not provided.');
}
const self = this;
if (!Array.isArray(emitters)) {
emitters = [
emitters
];
}
if (typeof events === 'string') {
events = [
events
];
}
let releaseFuncs = [];
events.forEach(function createProxy(event) {
emitters.forEach(function onClientEvent(emitter) {
const onEvent = function onEvent() {
//get event arguments
const argumentsArray = Array.prototype.slice.call(arguments, 0);
//add event name to list
argumentsArray.unshift(event);
//emit event
self.emit.apply(self, argumentsArray);
};
emitter.on(event, onEvent);
releaseFuncs.push(function stop() {
emitter.removeListener(event, onEvent);
});
});
});
return function stopProxy() {
if (releaseFuncs && releaseFuncs.length) {
releaseFuncs.forEach(function invokeStop(release) {
release();
});
releaseFuncs = null;
}
};
};
/**
* Adds empty event handler.
*
* @function
* @memberof! EnhancedEventEmitter
* @public
* @param {String} event - The name of the event
* @returns {function} The remove listener function
* @example
* ```js
* const EnhancedEventEmitter = EventEmitterEnhancer.extend(EventEmitter);
* const emitter = new EnhancedEventEmitter();
*
* //add noop even handler for the 'error' event
* const remove = emitter.addNoop('error');
*
* //remove listener
* remove();
* ```
*/
EnhancedEventEmitter.prototype.addNoop = function (event) {
let remove;
if (event && (typeof event === 'string')) {
remove = this.on(event, funcs.noop);
}
return remove;
};
/**
* Adds empty error event handler to prevent node.js from crashing in case of an error which we do not want/need to handle.<br>
* This function will only add a new empty handler in case no other handler is defined for the error event.
*
* @function
* @memberof! EnhancedEventEmitter
* @public
* @example
* ```js
* const EnhancedEventEmitter = EventEmitterEnhancer.extend(EventEmitter);
* const emitter = new EnhancedEventEmitter();
*
* //adds empty error handler
* emitter.ignoreError();
*
* //emit error will not crash the node.js process
* emitter.emit('error', new Error('test'));
* ```
*/
EnhancedEventEmitter.prototype.ignoreError = function () {
if (!this.listenerCount('error')) {
this.addNoop('error');
}
};
/**
* Converts the provided listener function to be async.
*
* @function
* @memberof! EnhancedEventEmitter
* @private
* @param {function} listener - The listener function
* @returns {function} The async listener
* @example
* ```js
* const asyncListner = emitter.asAsyncListener(listner);
* emitter.on('my_event', asyncListner);
* ```
*/
EnhancedEventEmitter.prototype.asAsyncListener = function (listener) {
let onEvent;
if (listener && (typeof listener === 'function')) {
onEvent = function () {
const argumentsArray = arguments;
defer(function invokeListener() {
listener.apply(listener, argumentsArray);
});
};
} else {
throw new Error('Missing mandatory parameters');
}
return onEvent;
};
/**
* Returns true if to allow to emit the event based on the currently setup filters.
*
* @function
* @memberof! EnhancedEventEmitter
* @private
* @param {Array} emitArguments - All emit function arguments array
* @returns {Boolean} True to continue with the emit, false to prevent it
*/
EnhancedEventEmitter.prototype.runFilterChain = function (emitArguments) {
if (this.filters) {
//first check global filters
let filter;
if (this.filters.global && this.filters.global.length) {
for (let index = 0; index < this.filters.global.length; index++) {
filter = this.filters.global[index];
if (!filter.apply(filter, emitArguments)) {
return false;
}
}
}
//get event
const event = emitArguments[0];
//check event specific filters
if (this.filters.event && this.filters.event[event] && this.filters.event[event].length) {
for (let index = 0; index < this.filters.event[event].length; index++) {
filter = this.filters.event[event][index];
if (!filter.apply(filter, emitArguments)) {
return false;
}
}
}
}
return true;
};
/**
* Marks the given event in the events map.
*
* @function
* @memberof! EnhancedEventEmitter
* @private
* @param {String} event - The event name to mark
* @param {Object} [events] - The events map
* @returns {Object} The updated events map
*/
EnhancedEventEmitter.prototype.markEvent = function (event, events) {
if (event) {
events = events || {};
events[event] = true;
}
return events;
};
module.exports = EnhancedEventEmitter;