async-cancelable-events
Advanced tools
Comparing version 0.0.1 to 0.0.2
var isAsync = require('is-async'); | ||
// ## printMaxListenersWarning | ||
// is a listener that prints a warning when the max listeners has been exceeded just | ||
// like the ``EventEmitter`` does, but now you can clear it out by executing | ||
// ``this.removeAllListeners('maxListenersPassed')`` in your constructor function | ||
// just after running the ``AsyncCancelableEvents`` constructor function in your own | ||
// constructor function. | ||
function printMaxListenersWarning(eventName, listenerCount) { | ||
console.warn('The Event ' + eventName + ' has exceeded ' + this._maxListeners + ' listeners, currently at ' + listenerCount); | ||
} | ||
// # AsyncCancelableEvents | ||
// constructor function, a drop-in replacement for ``EventEmitter`` almost every time | ||
// (excepting when an event emits a function, then use ``emitSync``) that also allows | ||
// the event listeners to cancel events if ``emit`` is provided with a callback. | ||
// Event listeners may be either synchronous like DOM events or asynchronous to better | ||
// fit the Node.js style. (Because of that, making an event placed inside of a high | ||
// performance loop is probably a bad idea because the event listeners can introduce | ||
// an unpredictable delay.) The ``AsyncCancelableEvents`` constructor function attaches | ||
// three "please don't touch" properties to itself (or its inheriting object) to keep | ||
// track of listeners and the maximum number of allowed listeners. | ||
function AsyncCancelableEvents() { | ||
@@ -7,8 +27,23 @@ this._eventListeners = {}; | ||
this._maxListeners = 10; | ||
this.on('maxListenersPassed', printMaxListenersWarning.bind(this)); | ||
} | ||
// ## emit | ||
// takes and event name and a variable number of arguments. If the last argument is a | ||
// function, it assumes it's a callback for the event and that this event is cancelable. | ||
// Listeners can only cancel events if their return value (or callback parameter) is | ||
// ``false``, any other value is considered ``true`` and the event continues. Event | ||
// cancelation immediately ends all processing of event listeners. The order of event | ||
// listeners is roughly that of the order they were attached, but ``once`` listeners | ||
// are given priority over ``on`` listeners (so its possible to do a one-time | ||
// cancelation and not have any of the ``on`` listeners called). | ||
AsyncCancelableEvents.prototype.emit = function emit(eventName) { | ||
// Get the list of arguments for the listeners into an array | ||
var argsArray = Array.prototype.slice.call(arguments, 1, arguments.length); | ||
// If the last argument is a function, assume its a callback for ``emit`` and pop it off | ||
var callback = argsArray[argsArray.length-1] instanceof Function ? argsArray[argsArray.length-1] : undefined; | ||
if(callback) argsArray.pop(); | ||
// Create an array of listeners to call for this particular event, taking the ``once`` | ||
// listeners first and firing the removeListener event, then attaching any ``on`` | ||
// listeners. In both cases only if they exist. | ||
var listenersToCall = []; | ||
@@ -23,2 +58,8 @@ if(this._oneTimeListeners[eventName] instanceof Array) { | ||
if(this._eventListeners[eventName] instanceof Array) Array.prototype.push.apply(listenersToCall, this._eventListeners[eventName]); | ||
// An asynchronous iterator for the listeners. If the return value given to it is ``false``, | ||
// immediately end and call the callback, if it exists, otherwise if there are still listeners | ||
// to call, call them (determining whether or not the listener is asynchronous) and pass the | ||
// result into the iterator function (either by callback or simply calling itself recursively | ||
// if synchronous). If there are no more listeners to call and none of them returned ``false``, | ||
// finally return ``true`` to the callback function, if it exists. | ||
var iterate = function iterate(retVal) { | ||
@@ -44,8 +85,19 @@ if(retVal === false) { | ||
argsArray.push(iterate); | ||
// Call the iterate function with ``true`` to kick off the recursive process | ||
iterate(true); | ||
// Whenever possible, have the methods return ``this`` so they can be chained | ||
return this; | ||
}; | ||
// ## emitAsync | ||
// In this case, forcing async means the same thing as the normal version of the function | ||
AsyncCancelableEvents.prototype.emitAsync = AsyncCancelableEvents.prototype.emit; | ||
// ## emitSync | ||
// Unfortunately for synchronous emit, the logic is changed enough that simply wrapping | ||
// ``sync`` (as done below with other methods) is not possible, but the overall pattern | ||
// of getting the arguments, the listeners to call, and iterating through them is still | ||
// the same. In this case, however, the last argument is always passed to the listeners | ||
// and what the listeners return is only checked for canceling the listeners calling list, | ||
// the result is never returned. | ||
AsyncCancelableEvents.prototype.emitSync = function emitSync(eventName) { | ||
@@ -82,2 +134,11 @@ var argsArray = Array.prototype.slice.call(arguments, 1, arguments.length); | ||
// ## on | ||
// The bread-n-butter of an EventEmitter, registering listeners for events. In this | ||
// implementation, the event types are lazily determined, as this is the most | ||
// obvious implementation for Javascript, and high performance for registering | ||
// listeners shouldn't be an issue. If the total number of listeners exceeds the | ||
// maximum specified, the ``maxListenersPassed`` event is fired with the event name | ||
// and listener count as arguments provided to the listener. Like all events internal | ||
// to ``AsyncCancelableEvents``, it is not cancelable, itself. Once registered, the | ||
// ``newListener`` event is emitted. | ||
AsyncCancelableEvents.prototype.on = function on(eventName, listener) { | ||
@@ -93,2 +154,6 @@ if(!this._eventListeners) this._eventListeners = {}; | ||
// ## onSync | ||
// Taking advantage of the ``is-async`` method's behavior, registering a forced | ||
// synchronous event simply requires attaching a truthy ``sync`` property to the | ||
// method and then registering it as usual. | ||
AsyncCancelableEvents.prototype.onSync = function onSync(eventName, listener) { | ||
@@ -99,2 +164,4 @@ listener.sync = true; | ||
// ## onAsync | ||
// Similarly for forced asynchronous functions, just attach a truthy ``async`` property | ||
AsyncCancelableEvents.prototype.onAsync = function onAsync(eventName, listener) { | ||
@@ -105,2 +172,6 @@ listener.async = true; | ||
// ## addListener, addListenerSync, addListenerAsync | ||
// These methods are just synonyms for the ``on*`` methods, kept just to make sure | ||
// ``AsyncCancelableEvents`` should "just work" for the majority of use-cases of | ||
// ``EventEmitter``. | ||
AsyncCancelableEvents.prototype.addListener = AsyncCancelableEvents.prototype.on; | ||
@@ -110,2 +181,6 @@ AsyncCancelableEvents.prototype.addListenerSync = AsyncCancelableEvents.prototype.onSync; | ||
// ## once | ||
// The ``once`` method works very similar to the ``on`` method, except it operates on the | ||
// ``_oneTimeListeners`` property, instead. This could probably be DRYed out a bit, but | ||
// will only save a few lines of code and I don't expect this library to change much. | ||
AsyncCancelableEvents.prototype.once = function once(eventName, listener) { | ||
@@ -121,2 +196,5 @@ if(!this._oneTimeListeners) this._oneTimeListeners = {}; | ||
// ## onceSync | ||
// Exactly like ``onSync``, but with a different method involved. Attempting to DRY | ||
// this would produce a net *gain* in lines of code. | ||
AsyncCancelableEvents.prototype.onceSync = function onceSync(eventName, listener) { | ||
@@ -127,2 +205,4 @@ listener.sync = true; | ||
// ## onceAsync | ||
// Exactly like ``onAsync`` but for ``once``. | ||
AsyncCancelableEvents.prototype.onceAsync = function onceAsync(eventName, listener) { | ||
@@ -133,2 +213,6 @@ listener.async = true; | ||
// ## removeListener | ||
// Checks if the event has any listeners registered and then filters out the provided listener, | ||
// firing the ``removeListener`` event if found. It must be a reference to the same function, | ||
// not just have equal ``toString`` results. | ||
AsyncCancelableEvents.prototype.removeListener = function removeListener(eventName, listener) { | ||
@@ -154,2 +238,6 @@ if(this._eventListeners[eventName] instanceof Array) this._eventListeners[eventName] = this._eventListeners[eventName].filter(function(registeredListener) { | ||
// ## removeAllListeners | ||
// If an event name is provided, it removes all of the listeners for that event (if the event | ||
// exists) and emits the ``removeListener`` event for each listener. If not called with an | ||
// event name, it generates a list of events and recursively calls itself for each event. | ||
AsyncCancelableEvents.prototype.removeAllListeners = function removeAllListeners(eventName) { | ||
@@ -180,2 +268,4 @@ if(eventName) { | ||
// ## setMaxListeners | ||
// 'Nuff said. | ||
AsyncCancelableEvents.prototype.setMaxListeners = function setMaxListeners(count) { | ||
@@ -186,2 +276,6 @@ this._maxListeners = count; | ||
// ## listeners | ||
// Returns an array of registered listeners for the given event. This array is a copy | ||
// so you can alter it at will, but the functions it points to are not copies, so | ||
// properties attached to them will propagate back into ``AsyncCancelableEvents``. | ||
AsyncCancelableEvents.prototype.listeners = function listeners(eventName) { | ||
@@ -194,2 +288,6 @@ var registeredListeners = []; | ||
// ## listenerCount | ||
// Not sure why ``EventEmitter`` didn't make this a prototype method, but here you go. | ||
// Given an ``AsyncCancelableEvents`` instance and an event name, it returns the number | ||
// of listeners registered on that event. | ||
AsyncCancelableEvents.listenerCount = function listenerCount(instance, eventName) { | ||
@@ -202,2 +300,3 @@ var count = 0; | ||
// Export ``AsyncCancelableEvents`` | ||
module.exports = AsyncCancelableEvents; |
@@ -1,1 +0,1 @@ | ||
(function(e,t,n){function r(n,s){if(!t[n]){if(!e[n]){var o=typeof require=="function"&&require;if(!s&&o)return o(n,!0);if(i)return i(n,!0);throw new Error("Cannot find module '"+n+"'")}var u=t[n]={exports:{}};e[n][0](function(t){var i=e[n][1][t];return r(i?i:t)},u,u.exports)}return t[n].exports}var i=typeof require=="function"&&require;for(var s=0;s<n.length;s++)r(n[s]);return r})({1:[function(e,t,n){function i(){this._eventListeners={},this._oneTimeListeners={},this._maxListeners=10}var r=e("is-async");i.prototype.emit=function(t){var n=Array.prototype.slice.call(arguments,1,arguments.length),i=n[n.length-1]instanceof Function?n[n.length-1]:undefined;i&&n.pop();var s=[];this._oneTimeListeners[t]instanceof Array&&(s=this._oneTimeListeners[t],delete this._oneTimeListeners[t],s.forEach(function(e){this.emitSync("removeListener",e)}.bind(this))),this._eventListeners[t]instanceof Array&&Array.prototype.push.apply(s,this._eventListeners[t]);var o=function u(e){if(e===!1)i&&i(!1);else if(s.length){var t=s.shift();if(r(t,n.length))t.apply(null,n);else{n.pop();var o=t.apply(null,n);n.push(u),u(o)}}else i&&i(!0)};return n.push(o),o(!0),this},i.prototype.emitAsync=i.prototype.emit,i.prototype.emitSync=function(t){var n=Array.prototype.slice.call(arguments,1,arguments.length),i=[];this._oneTimeListeners[t]instanceof Array&&(i=this._oneTimeListeners[t],delete this._oneTimeListeners[t],i.forEach(function(e){this.emitSync("removeListener",e)}.bind(this))),this._eventListeners[t]instanceof Array&&Array.prototype.push.apply(i,this._eventListeners[t]);var s=function o(e){if(e!==!1&&i.length){var t=i.shift();if(r(t,n.length))t.apply(null,n);else{n.pop();var s=t.apply(null,n);n.push(o),o(s)}}};return n.push(s),s(!0),this},i.prototype.on=function(t,n){this._eventListeners||(this._eventListeners={}),this._eventListeners[t]=this._eventListeners[t]instanceof Array?this._eventListeners[t]:[],this._eventListeners[t].push(n);var r=this._eventListeners[t].length+(this._oneTimeListeners[t]instanceof Array?this._oneTimeListeners[t].length:0);return r>this._maxListeners&&this.emitSync("maxListenersPassed",t,r),this.emitSync("newListener",n),this},i.prototype.onSync=function(t,n){return n.sync=!0,this.on(t,n)},i.prototype.onAsync=function(t,n){return n.async=!0,this.on(t,n)},i.prototype.addListener=i.prototype.on,i.prototype.addListenerSync=i.prototype.onSync,i.prototype.addListenerAsync=i.prototype.onAsync,i.prototype.once=function(t,n){this._oneTimeListeners||(this._oneTimeListeners={}),this._oneTimeListeners[t]=this._oneTimeListeners[t]instanceof Array?this._oneTimeListeners[t]:[],this._oneTimeListeners[t].push(n);var r=this._oneTimeListeners[t].length+(this._eventListeners[t]instanceof Array?this._eventListeners[t].length:0);return r>this._maxListeners&&this.emitSync("maxListenersPassed",t,r),this.emitSync("newListener",n),this},i.prototype.onceSync=function(t,n){return n.sync=!0,this.once(t,n)},i.prototype.onceAsync=function(t,n){return n.async=!0,this.once(t,n)},i.prototype.removeListener=function(t,n){return this._eventListeners[t]instanceof Array&&(this._eventListeners[t]=this._eventListeners[t].filter(function(e){return e===n?(this.emitSync("removeListener",n),!1):!0}.bind(this))),this._oneTimeListeners[t]instanceof Array&&(this._oneTimeListeners[t]=this._oneTimeListeners[t].filter(function(e){return e===n?(this.emitSync("removeListener",n),!1):!0}.bind(this))),this},i.prototype.removeAllListeners=function(t){return t?(this._eventListeners[t]instanceof Array&&(this._eventListeners[t].forEach(function(e){this.emitSync("removeListener",e)}.bind(this)),delete this._eventListeners[t]),this._oneTimeListeners[t]instanceof Array&&(this._oneTimeListeners[t].forEach(function(e){this.emitSync("removeListener",e)}.bind(this)),delete this._oneTimeListeners[t])):(Object.keys(this._eventListeners).forEach(function(e){this.removeAllListeners(e)}.bind(this)),Object.keys(this._oneTimeListeners).forEach(function(e){this.removeAllListeners(e)}.bind(this))),this},i.prototype.setMaxListeners=function(t){return this._maxListeners=t,this},i.prototype.listeners=function(t){var n=[];return this._eventListeners[t]instanceof Array&&Array.prototype.push.apply(n,this._eventListeners[t]),this._oneTimeListeners[t]instanceof Array&&Array.prototype.push.apply(n,this._oneTimeListeners[t]),n},i.listenerCount=function(t,n){var r=0;return t._eventListeners[n]instanceof Array&&(r+=t._eventListeners[n].length),t._oneTimeListeners[n]instanceof Array&&(r+=t._oneTimeListeners[n].length),r},t.exports=i},{"is-async":2}],2:[function(e,t,n){function r(e,t){return e.async||!e.sync&&e.length===t}t.exports=r},{}]},{},[1]); | ||
(function(e,t,n){function r(n,s){if(!t[n]){if(!e[n]){var o=typeof require=="function"&&require;if(!s&&o)return o(n,!0);if(i)return i(n,!0);throw new Error("Cannot find module '"+n+"'")}var u=t[n]={exports:{}};e[n][0](function(t){var i=e[n][1][t];return r(i?i:t)},u,u.exports)}return t[n].exports}var i=typeof require=="function"&&require;for(var s=0;s<n.length;s++)r(n[s]);return r})({1:[function(e,t,n){(function(){function r(e,t){console.warn("The Event "+e+" has exceeded "+this._maxListeners+" listeners, currently at "+t)}function i(){this._eventListeners={},this._oneTimeListeners={},this._maxListeners=10,this.on("maxListenersPassed",r.bind(this))}var n=e("is-async");i.prototype.emit=function(t){var r=Array.prototype.slice.call(arguments,1,arguments.length),i=r[r.length-1]instanceof Function?r[r.length-1]:undefined;i&&r.pop();var s=[];this._oneTimeListeners[t]instanceof Array&&(s=this._oneTimeListeners[t],delete this._oneTimeListeners[t],s.forEach(function(e){this.emitSync("removeListener",e)}.bind(this))),this._eventListeners[t]instanceof Array&&Array.prototype.push.apply(s,this._eventListeners[t]);var o=function u(e){if(e===!1)i&&i(!1);else if(s.length){var t=s.shift();if(n(t,r.length))t.apply(null,r);else{r.pop();var o=t.apply(null,r);r.push(u),u(o)}}else i&&i(!0)};return r.push(o),o(!0),this},i.prototype.emitAsync=i.prototype.emit,i.prototype.emitSync=function(t){var r=Array.prototype.slice.call(arguments,1,arguments.length),i=[];this._oneTimeListeners[t]instanceof Array&&(i=this._oneTimeListeners[t],delete this._oneTimeListeners[t],i.forEach(function(e){this.emitSync("removeListener",e)}.bind(this))),this._eventListeners[t]instanceof Array&&Array.prototype.push.apply(i,this._eventListeners[t]);var s=function o(e){if(e!==!1&&i.length){var t=i.shift();if(n(t,r.length))t.apply(null,r);else{r.pop();var s=t.apply(null,r);r.push(o),o(s)}}};return r.push(s),s(!0),this},i.prototype.on=function(t,n){this._eventListeners||(this._eventListeners={}),this._eventListeners[t]=this._eventListeners[t]instanceof Array?this._eventListeners[t]:[],this._eventListeners[t].push(n);var r=this._eventListeners[t].length+(this._oneTimeListeners[t]instanceof Array?this._oneTimeListeners[t].length:0);return r>this._maxListeners&&this.emitSync("maxListenersPassed",t,r),this.emitSync("newListener",n),this},i.prototype.onSync=function(t,n){return n.sync=!0,this.on(t,n)},i.prototype.onAsync=function(t,n){return n.async=!0,this.on(t,n)},i.prototype.addListener=i.prototype.on,i.prototype.addListenerSync=i.prototype.onSync,i.prototype.addListenerAsync=i.prototype.onAsync,i.prototype.once=function(t,n){this._oneTimeListeners||(this._oneTimeListeners={}),this._oneTimeListeners[t]=this._oneTimeListeners[t]instanceof Array?this._oneTimeListeners[t]:[],this._oneTimeListeners[t].push(n);var r=this._oneTimeListeners[t].length+(this._eventListeners[t]instanceof Array?this._eventListeners[t].length:0);return r>this._maxListeners&&this.emitSync("maxListenersPassed",t,r),this.emitSync("newListener",n),this},i.prototype.onceSync=function(t,n){return n.sync=!0,this.once(t,n)},i.prototype.onceAsync=function(t,n){return n.async=!0,this.once(t,n)},i.prototype.removeListener=function(t,n){return this._eventListeners[t]instanceof Array&&(this._eventListeners[t]=this._eventListeners[t].filter(function(e){return e===n?(this.emitSync("removeListener",n),!1):!0}.bind(this))),this._oneTimeListeners[t]instanceof Array&&(this._oneTimeListeners[t]=this._oneTimeListeners[t].filter(function(e){return e===n?(this.emitSync("removeListener",n),!1):!0}.bind(this))),this},i.prototype.removeAllListeners=function(t){return t?(this._eventListeners[t]instanceof Array&&(this._eventListeners[t].forEach(function(e){this.emitSync("removeListener",e)}.bind(this)),delete this._eventListeners[t]),this._oneTimeListeners[t]instanceof Array&&(this._oneTimeListeners[t].forEach(function(e){this.emitSync("removeListener",e)}.bind(this)),delete this._oneTimeListeners[t])):(Object.keys(this._eventListeners).forEach(function(e){this.removeAllListeners(e)}.bind(this)),Object.keys(this._oneTimeListeners).forEach(function(e){this.removeAllListeners(e)}.bind(this))),this},i.prototype.setMaxListeners=function(t){return this._maxListeners=t,this},i.prototype.listeners=function(t){var n=[];return this._eventListeners[t]instanceof Array&&Array.prototype.push.apply(n,this._eventListeners[t]),this._oneTimeListeners[t]instanceof Array&&Array.prototype.push.apply(n,this._oneTimeListeners[t]),n},i.listenerCount=function(t,n){var r=0;return t._eventListeners[n]instanceof Array&&(r+=t._eventListeners[n].length),t._oneTimeListeners[n]instanceof Array&&(r+=t._oneTimeListeners[n].length),r},t.exports=i})()},{"is-async":2}],2:[function(e,t,n){function r(e,t){return e.async||!e.sync&&e.length===t}t.exports=r},{}]},{},[1]); |
@@ -7,3 +7,3 @@ { | ||
"author": "David Ellis", | ||
"version": "0.0.1", | ||
"version": "0.0.2", | ||
"bugs": "https://github.com/dfellis/async-cancelable-events/issues", | ||
@@ -10,0 +10,0 @@ "repository": { |
@@ -19,2 +19,10 @@ # async-cancelable-events | ||
var EventEmitter = require('async-cancelable-events'); | ||
var util = require('util'); | ||
function MyEmittingObject() { | ||
EventEmitter.call(this); | ||
... | ||
} | ||
util.inherits(MyEmittingObject, EventEmitter); | ||
``` | ||
@@ -28,3 +36,3 @@ | ||
2. The ``.on`` and ``.once`` methods try to "guess" if the provided handler is synchronous or asynchronous (based on its argument length), or can be explicitly registered as synchronous or asynchronous with ``.onSync``, ``.onAsync``, ``.onceSync``, ``.onceAsync``. | ||
3. Did you know ``.addListener`` was a thing? I didn't. It's just a synonmym for ``.on``, by the way. | ||
3. Passing the maximum number of listeners allowed will fire off a ``maxListenersPassed`` event with the event name and listener count as arguments. The warning the official ``EventEmitter`` prints is simply a listener for ``async-cancelable-events``, and can be disabled by running ``this.removeAllListeners('maxListenersPassed')`` just after the ``EventEmitter.call(this)`` listed above. | ||
4. The various method calls are chainable, so ``foo.on('bar', func1).on('baz', func2)`` is valid. | ||
@@ -31,0 +39,0 @@ |
@@ -137,5 +137,5 @@ var fs = require('fs'); | ||
bootstrap(test); | ||
test.expect(12); | ||
test.expect(15); | ||
var emitter = new EventEmitter(); | ||
emitter.setMaxListeners(1); | ||
emitter.setMaxListeners(2); | ||
emitter.on('newListener', function(listener) { | ||
@@ -146,3 +146,3 @@ test.ok(listener instanceof Function, 'got a new listener'); | ||
test.equal(eventName, 'someEvent', 'added too many listeners to someEvent'); | ||
test.equal(count, 2, "this town ain't big enough for the two of us!"); | ||
test.equal(count, 3, "this town ain't big enough for the two of us!"); | ||
}); | ||
@@ -158,2 +158,5 @@ emitter.on('removeListener', function(listener) { | ||
}); | ||
emitter.once('someEvent', function() { | ||
test.ok(true, 'third listener fired'); | ||
}); | ||
emitter.emit('someEvent', function() { | ||
@@ -160,0 +163,0 @@ test.ok(true, 'event finished'); |
Sorry, the diff of this file is not supported yet
97613
697
59