@silvermine/event-emitter
Advanced tools
Comparing version 1.0.1 to 2.0.0
{ | ||
"include": [ | ||
"lib/**/*.js" | ||
"src/**/*.js" | ||
], | ||
@@ -5,0 +5,0 @@ "extension": [ |
{ | ||
"name": "@silvermine/event-emitter", | ||
"version": "1.0.1", | ||
"version": "2.0.0", | ||
"description": "a simple event emitter mixin that is well-tested, solid, and dependency-free", | ||
"main": "src/index.js", | ||
"scripts": { | ||
"commitlint": "commitlint --from 9f45934", | ||
"commitlint": "commitlint --from c798aab", | ||
"test": "check-node-version --npm 6.14.12 --print && nyc mocha -- 'tests/**/*.test.js'" | ||
@@ -30,2 +30,3 @@ }, | ||
"homepage": "https://github.com/silvermine/event-emitter#readme", | ||
"types": "src/types/index.d.ts", | ||
"devDependencies": { | ||
@@ -32,0 +33,0 @@ "@silvermine/eslint-config": "3.0.1", |
@@ -21,7 +21,7 @@ # Silvermine Event Emitter | ||
browser. For server-side usage, simply use the built-in `require` function to `require | ||
('silvermine-event-emitter')` and use the mixin. For browser environments, use a CommonJS | ||
('@silvermine/event-emitter')` and use the mixin. For browser environments, use a CommonJS | ||
module loader like [Browserify](http://browserify.org/) or | ||
[Webpack](https://webpack.github.io/). | ||
`silvermine-event-emitter` uses native [Promises][] to make event listener executions | ||
`@silvermine/event-emitter` uses native [Promises][] to make event listener executions | ||
asynchronous. Native Promises are available in node.js versions `0.12.18` and up, and in | ||
@@ -32,3 +32,3 @@ [most browsers](http://caniuse.com/#feat=promises) except Internet Explorer. If you need | ||
`silvermine-event-emitter` also uses these built-in `Array` methods that are only | ||
`@silvermine/event-emitter` also uses these built-in `Array` methods that are only | ||
available in Internet Explorer 9 and up: | ||
@@ -44,3 +44,3 @@ | ||
The object returned by `require('silvermine-event-emitter')` is designed to be used as a | ||
The object returned by `require('@silvermine/event-emitter')` is designed to be used as a | ||
mixin for javascript "classes". Here are a few different ways in which the mixin can be | ||
@@ -50,4 +50,4 @@ used: | ||
```js | ||
var EventEmitterMixin = require('silvermine-event-emitter'), | ||
MyClass, myInstance; | ||
const { EventEmitterMixin } = require('@silvermine/event-emitter').EventEmitterMixin, | ||
MyClass, myInstance; | ||
@@ -68,3 +68,3 @@ MyClass = function() {}; | ||
```js | ||
var EventEmitterMixin = require('silvermine-event-emitter'), | ||
var EventEmitterMixin = require('@silvermine/event-emitter').EventEmitterMixin, | ||
myInstance; | ||
@@ -84,5 +84,4 @@ | ||
```js | ||
const { EventEmitterMixin } = require('@silvermine/event-emitter'); | ||
let EventEmitterMixin = require('silvermine-event-emitter'); | ||
let MixinEventEmitter = (Base) => { | ||
@@ -101,3 +100,2 @@ Object.assign(Base.prototype, EventEmitterMixin); | ||
myInstance.emit('started'); | ||
``` | ||
@@ -210,4 +208,3 @@ | ||
You may want to bind one event listener to several event names. You can either bind them | ||
individually or within a single call to `on` by passing a space-delimited list of event | ||
names: | ||
individually or within a single call to `on` by passing an array of event names: | ||
@@ -217,3 +214,3 @@ ```js | ||
myInstance.on('started stopped paused', onChange); | ||
myInstance.on([ 'started', 'stopped', 'paused' ], onChange); | ||
@@ -228,7 +225,7 @@ // or: | ||
You can emit multiple events with a single call to `emit` by passing a space-delimited | ||
list of event names: | ||
You can emit multiple events with a single call to `emit` by passing an array of event | ||
names: | ||
```js | ||
myInstance.emit('initialized started played'); | ||
myInstance.emit([ 'initialized', 'started', 'played' ]); | ||
``` | ||
@@ -315,2 +312,29 @@ | ||
##### A warning about arrow functions | ||
JavaScript does not allow you to re-bind the `this` context of an arrow function. If you | ||
pass an arrow function as a listener, the `context` parameter will have no effect. For | ||
example: | ||
```js | ||
const outerContext = this; | ||
function registerListeners() { | ||
// The `outerContext` parameter will be ignored. The listener will have the `this` | ||
// context that `registerListeners` is called with. | ||
myInstance.on('started', () => { console.log('Started', this.name); }, outerContext); | ||
} | ||
``` | ||
If you need to re-bind the context, use a `function` statement instead: | ||
```js | ||
const outerContext = this; | ||
function registerListeners() { | ||
// The listener function will have `outerContext` as its `this` context. | ||
myInstance.on('started', function() { console.log('Started', this.name); }, outerContext); | ||
} | ||
``` | ||
## How do I contribute? | ||
@@ -317,0 +341,0 @@ |
246
src/index.js
@@ -5,65 +5,7 @@ 'use strict'; | ||
reject = require('./lib/reject'), | ||
isArrayOfStrings = require('./lib/is-array-of-strings'), | ||
EventEmitterMixin; | ||
/** | ||
* Can be used as a mixin when making a class that needs to be an EventEmitter. | ||
* | ||
* e.g. `Object.assign(MyClass.prototype, EventEmitterMixin);` | ||
* | ||
* Note: if it is necessary to override a listener function's `this` context, always use | ||
* the optional `context` parameter on the {@link EventEmitterMixin#on} method to do so | ||
* if you want to remove that specific listener or listener and context combination | ||
* later. If you are using {@link EventEmitterMixin#once} or never need to remove the | ||
* event listener, using `listener.bind(context)` instead of the context parameter is | ||
* acceptable. | ||
* | ||
* It is common to override a listener function's `this` context using the `Function` | ||
* object's `bind` method. For example: | ||
* | ||
* ``` | ||
* emitter.on('ready', this.onReady.bind(this)); | ||
* ``` | ||
* | ||
* However, doing so will make it impossible to remove that listener function without | ||
* calling `emitter.off('ready')`, which would remove **all** listeners for the `ready` | ||
* event. | ||
* | ||
* This happens because calling `.bind(context)` on a function produces a completely | ||
* new `Function` instance. When it's time to remove an event listener that was bound | ||
* to a context using the `bind` function, calling `bind` on the same function will | ||
* produce a different instance that does not pass an equality check with the | ||
* previously bound function. For example: | ||
* | ||
* ``` | ||
* var fn = function() {}, | ||
* context = {}; | ||
* | ||
* fn === fn; // true | ||
* fn.bind(context) === fn.bind(context); // false | ||
* ``` | ||
* | ||
* And so: | ||
* | ||
* ``` | ||
* emitter.on('ready', fn.bind(context)); | ||
* emitter.off('ready', fn.bind(context)); | ||
* ``` | ||
* | ||
* does not remove the event listener that is listening to `'ready'` which results in a | ||
* memory leak. The correct way is to use the third argument to `on`, which lets you | ||
* specify the context for the `listener` function: | ||
* | ||
* ``` | ||
* emitter.on('ready', fn, context); | ||
* ``` | ||
* | ||
* Then, to remove that particular listener, call {@link EventEmitterMixin#off} and pass | ||
* the same event name, function, and context: | ||
* | ||
* ``` | ||
* emitter.off('ready', fn, context); | ||
* ``` | ||
* | ||
* @mixin | ||
*/ | ||
// See the full JSDoc documentation for this object and its public methods in | ||
// ./types/index.d.ts | ||
EventEmitterMixin = { | ||
@@ -82,42 +24,7 @@ | ||
/** | ||
* Register a listener function that will be called every time the specified event is | ||
* emitted. | ||
* | ||
* Calls to `on` will de-duplicate listeners so that the same listener and context | ||
* combination does not get invoked more than once for the same event. Also, calls | ||
* to `on` override calls to {@link EventEmitterMixin#once} in that if there is still | ||
* an event listener and context combination registered from a call to | ||
* {@link EventEmitterMixin#once} and the same listener and context combination is | ||
* passed to a call to `on`, that listener and context combination will **not** be | ||
* removed after the first event. | ||
* | ||
* If the `listener` function (or the listener function and its associated `context`) | ||
* was already registered using {@link EventEmitterMixin#on} or | ||
* {@link EventEmitterMixin#once}, registering it again with `on` will have the | ||
* following effect: | ||
* | ||
* * `on`: if it was registered with `on`, nothing happens. There remains one | ||
* listener registered for the `eventNames` event(s). | ||
* * `once`: if it was registered with `once`, and the `eventName` event has not | ||
* been emitted yet, then that listener becomes an `on` listener, is executed each | ||
* time that the event is emitted, and is **not** removed after it has been called | ||
* once. | ||
* | ||
* @param eventNames {string} one or more names of the event(s) your listener will be | ||
* invoked for, when emitted. Providing a string of space-separated names will bind | ||
* the provided listener to each of the events listed. | ||
* @param listener {function} the listener that will be called when this event is | ||
* emitted | ||
* @param [context] {object} the object that will be the `this` context for the | ||
* `listener` function when it is executed. See the documentation on | ||
* {@link EventEmitterMixin} for an explanation of when and how to use this parameter. | ||
* @instance | ||
* @returns {object} `this` for chaining | ||
*/ | ||
on: function(eventNames, listener, context) { | ||
var eventNamesList; | ||
if (typeof eventNames !== 'string') { | ||
throw new Error('the eventNames parameter must be a string, but was: ' + (typeof eventNames)); | ||
if (typeof eventNames !== 'string' && !isArrayOfStrings(eventNames)) { | ||
throw new Error('the eventNames parameter must be a string or an array of strings, but was: ' + eventNames); | ||
} | ||
@@ -127,3 +34,3 @@ if (typeof listener !== 'function') { | ||
} | ||
eventNamesList = eventNames.split(' '); | ||
eventNamesList = Array.isArray(eventNames) ? eventNames : [ eventNames ]; | ||
@@ -197,60 +104,2 @@ // Remove the event listeners if they already exist. Listeners bound with this `on` | ||
/** | ||
* Register a listener function that will be called only once. After the listener is | ||
* invoked for the first time, it will be discarded. | ||
* | ||
* If the `listener` function or the `listener` function and context is already | ||
* registered using either {@link EventEmitterMixin#on} or | ||
* {@link EventEmitterMixin#once}, this operation essentially has no effect. | ||
* | ||
* Unlike the {@link EventEmitterMixin#on} function, this function can only register | ||
* a listener for one `eventName` at a time. This saves us from a large amount of | ||
* complexity in the EventEmitter API. For example: | ||
* | ||
* ``` | ||
* var listener = function() {}; | ||
* | ||
* eventEmitter | ||
* .once('a b c', listener) | ||
* .on('b', listener) | ||
* .emit('b'); | ||
* ``` | ||
* | ||
* Should there be one event listener bound for each of 'a', 'b', and 'c'? Or would | ||
* `listener` only execute one time for 'a' *or* 'b' *or* 'c'? Further, if the 'b' | ||
* event is emitted, as shown above, would you expect `listener` to be executed once, | ||
* or twice? If 'c' is then emitted after 'b', should `listener` be executed again, or | ||
* was it removed as the result of emitting 'b'? Even a simple example raises many | ||
* questions with non-obvious answers. Allowing `once` to register only one event | ||
* listener at a time gives us a more straightforward API that is easy to understand | ||
* and reason about. | ||
* | ||
* If you would like to create a listener that will only execute once across multiple | ||
* event names, you can do so using the Underscore or Lodash library's `_.once` | ||
* function. For example: | ||
* | ||
* ``` | ||
* var listener = _.once(function() {}); | ||
* | ||
* eventEmitter | ||
* .once('a', listener) | ||
* .once('b', listener) | ||
* .once('c', listener); | ||
* ``` | ||
* | ||
* Then, when either the 'a', 'b', or 'c' events are emitted, the listener function | ||
* will be invoked once and will not be invoked again for any 'a', 'b', or 'c' events. | ||
* However, note that if the other two events are not emitted then `listener` remains | ||
* in memory. In the example above, if 'a' is emitted then the `listener` function | ||
* remains registered and in-memory for events 'b' and 'c' until both 'b' and 'c' | ||
* are emitted. | ||
* | ||
* @param eventName {string} the name of the event your listener will be invoked on. | ||
* @param listener {function} the listener that will be called the first time this | ||
* event is emitted | ||
* @param [context] {object} the object that will be the `this` context for the | ||
* `listener` function when it is executed | ||
* @instance | ||
* @returns {object} `this` for chaining | ||
*/ | ||
once: function(eventName, listener, context) { | ||
@@ -263,6 +112,2 @@ var self = this, | ||
} | ||
if (eventName.indexOf(' ') !== -1) { | ||
throw new Error('The eventName parameter cannot contain the name of more than one event and so it ' | ||
+ 'should not contain a space. The eventName parameter was: ' + eventName); | ||
} | ||
if (typeof listener !== 'function') { | ||
@@ -293,35 +138,5 @@ throw new Error('the listener parameter must be a function, but was: ' + (typeof listener)); | ||
/** | ||
* Removes event listeners. | ||
* | ||
* If this function is called with no parameters, then all event listeners bound to | ||
* this object will be removed. | ||
* | ||
* If only the `eventNames` parameter is provided, then all listeners bound to each | ||
* name in `eventNames` will be removed. | ||
* | ||
* If the `eventNames` and `listener` parameters only are provided, then all listeners | ||
* for each name in `eventNames` that use the given `listener` function will be | ||
* removed. | ||
* | ||
* If all three `eventNames`, `listener`, and `context` parameters are provided, for | ||
* each event name in `eventNames`, only the listener registered with that specific | ||
* event name, `listener` function, and context will be removed. | ||
* | ||
* @param [eventNames] {string} the name(s) of one or more events. Providing a string | ||
* of space-separated names will remove the listeners for each of the events listed. | ||
* Omitting this parameter will remove all event listeners from this object. | ||
* @param [listener] {function} the listener that will be removed. If this parameter | ||
* is not provided, then **all** event listeners listening to each `eventName` will be | ||
* removed. | ||
* @param [context] {object} the object that was provided as the `this` context for | ||
* the `listener` function when the event listener you are removing was registered. | ||
* See the documentation on {@link EventEmitterMixin} for an explanation of when and | ||
* how to use this parameter. If this parameter is not provided, then **all** event | ||
* listeners listening to each `eventName` using the given `listener` function will be | ||
* removed. | ||
* @instance | ||
* @returns {object} `this` for chaining | ||
*/ | ||
off: function(eventNames, listener, context) { | ||
var eventNamesList; | ||
if (!eventNames) { | ||
@@ -331,3 +146,5 @@ this._eventListeners = {}; | ||
} | ||
eventNames.split(' ').forEach(function(eventName) { | ||
eventNamesList = Array.isArray(eventNames) ? eventNames : [ eventNames ]; | ||
eventNamesList.forEach(function(eventName) { | ||
this._removeEventListener(eventName, listener, context); | ||
@@ -375,21 +192,14 @@ }.bind(this)); | ||
/** | ||
* Emits an event to any listeners registered for it. | ||
* | ||
* @param eventNames {string} the names of one or more events to emit. Providing a | ||
* string of space-separated names will emit each of the events listed. | ||
* @param * {...*} all other arguments will be passed to the event listeners | ||
* @instance | ||
* @returns {object} `this` for chaining | ||
*/ | ||
emit: function(eventNames) { | ||
var args = Array.prototype.slice.apply(arguments), | ||
eventArgs = args.slice(1); | ||
eventArgs = args.slice(1), | ||
eventNamesList; | ||
if (typeof eventNames !== 'string') { | ||
throw new Error('the eventNames parameter must be a string, but was: ' + (typeof eventNames)); | ||
if (typeof eventNames !== 'string' && !isArrayOfStrings(eventNames)) { | ||
throw new Error('the eventNames parameter must be a string or an array of strings, but was: ' + eventNames); | ||
} | ||
eventNames.split(' ').forEach(function(eventName) { | ||
eventNamesList = Array.isArray(eventNames) ? eventNames : [ eventNames ]; | ||
eventNamesList.forEach(function(eventName) { | ||
this._emitEvent(eventName, eventArgs); | ||
@@ -431,2 +241,20 @@ }.bind(this)); | ||
module.exports = EventEmitterMixin; | ||
// Export the EventEmitterMixin as an ES-Module-compatible named export. | ||
// | ||
// TypeScript and ES-Module users will import the EventEmitterMixin object using the | ||
// `import { EventEmitterMixin } from '@silvermine/event-emitter'` syntax and CommonJS | ||
// users can use the object destructuring syntax: `const { EventEmitterMixin } = | ||
// require('@silvermine/event-emitter')` | ||
// | ||
// Why not just use `module.exports = EventEmitterMixin` here as we did in v1.x? To add | ||
// proper TypeScript types for `module.exports = SOMETHING`, you have to use the `export = | ||
// SOMETHING` statement in your .d.ts file. TypeScript provides the `export =` statement | ||
// for exactly this use case. However, using it means you cannot export any other object, | ||
// type, or interface in that file. This means that we'd only be able to export the | ||
// `EventEmitterMixin` object itself, and not the `IEventEmitter` interface. That would be | ||
// very inconvenient for TypeScript users. | ||
// | ||
// Instead, we export the `EventEmitterMixin` as a named export. This allows us to export | ||
// both the `IEventEmitter` interface and the `EventEmitterMixin` object in | ||
// `./types/index.d.ts`. | ||
module.exports.EventEmitterMixin = EventEmitterMixin; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
35287
13
524
347