A small library that provides an event emitter mixin that can be added to your own JS classes / objects to allow them to implement a pubsub/listener/observer pattern by means of events.
There are a number of other libraries out there to do this, but many of them are either not well-tested or have some other deficiency. We needed a solid, quality event emitter mixin that we could use in a variety of projects.
The event emitter mixin is a CommonJS module, and will work both in node.js and in the
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
module loader like Browserify or
Webpack.
@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
most browsers except Internet Explorer. If you need
to support an environment that does not have native Promise support, you can easily add a
Promise polyfill.
@silvermine/event-emitter
also uses these built-in Array
methods that are only
available in Internet Explorer 9 and up:
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
used:
const { EventEmitterMixin } = require('@silvermine/event-emitter').EventEmitterMixin,
MyClass, myInstance;
MyClass = function() {};
// Copy functions from EventEmitterMixin to MyClass.prototype
Object.assign(MyClass.prototype, EventEmitterMixin);
// Or use underscore or lodash
_.extend(MyClass.prototype, EventEmitterMixin);
myInstance = new MyClass();
myInstance.emit('hello');
Alternatively, if you prefer to use objects directly, without classes:
var EventEmitterMixin = require('@silvermine/event-emitter').EventEmitterMixin,
myInstance;
myInstance = Object.create(EventEmitterMixin, {
doSomething: function() {}
});
myInstance.emit('hello');
If you are using ES6, see this guide on using mixins with ES6 classes. Here is an example of a mixin function that can be used to add event emitter methods to an ES6 class:
const { EventEmitterMixin } = require('@silvermine/event-emitter');
let MixinEventEmitter = (Base) => {
Object.assign(Base.prototype, EventEmitterMixin);
return Base;
};
class MyBaseClass {}
class MyClass extends MixinEventEmitter(MyBaseClass) {
doSomething() {}
}
let myInstance = new MyClass();
myInstance.emit('started');
Once you have an object that has the event emitter functions mixed in, you can begin
adding event listeners and emitting events. Use the on
function to add an event
listener, then use the emit
function to emit an event:
myInstance.on('started', function() {
console.log('myInstance started');
});
// Prints "myInstance started" to the console
myInstance.emit('started');
If you want an event listener to execute only once for a given event, use the once
function when adding the listener:
myInstance.once('started', function() {
console.log('myInstance started');
});
// Prints "myInstance started" to the console
myInstance.emit('started');
// Does nothing
myInstance.emit('started');
If you no longer want to listen to an event, use the off
function to remove an event
listener:
myInstance.on('started', function() {
console.log('myInstance started');
});
// Prints "myInstance started" to the console
myInstance.emit('started');
// Remove the event listener for the 'started' event
myInstance.off('started');
// Does nothing
myInstance.emit('started');
You can also remove all event listeners from an object by calling off
with no arguments:
myInstance.on('started', function() {
console.log('myInstance started');
});
myInstance.on('ended', function() {
console.log('myInstance ended');
});
// Remove all event listeners
myInstance.off();
// Does nothing
myInstance.emit('started');
myInstance.emit('ended');
All functions in event emitter are chainable; they return this
so that subsequent
function calls can chain off of each other:
function listener1() {}
function listener2() {}
myInstance.on('started', listener1)
.on('stopped', listener2)
.off('paused')
.emit('started')
You may want to pass arguments to listener functions when emitting an event. Any
arguments given to emit
after the event name(s) argument will be passed to any
listener functions:
function onStarted(instance, initialValue) {
console.log(instance, 'was started with an initialValue of', initialValue);
}
myInstance.on('started', onStarted);
myInstance.emit('started', myInstance, 42);
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 an array of event names:
function onChange {}
myInstance.on([ 'started', 'stopped', 'paused' ], onChange);
// or:
myInstance.on('started', onChange)
.on('stopped', onChange)
.on('paused', onChange);
You can emit multiple events with a single call to emit
by passing an array of event
names:
myInstance.emit([ 'initialized', 'started', 'played' ]);
If you add multiple event listeners to the same object for the same event name, you may
remove a single event listener without affecting the others by passing in the listener
function to the off
function:
function listener1 {}
function listener2 {}
function listener3 {}
myInstance.on('started', listener1);
myInstance.on('started', listener2);
myInstance.on('started', listener3);
myInstance.off('started', listener2);
// listener1 and listener3 are executed, but listener2 is not
myInstance.emit('started');
Sometimes you need a function to execute with a certain this
context. It may be tempting
to bind the context directly to the listener function:
myInstance.on('started', app.onStarted.bind(app));
However, using .bind(app)
makes it impossible to remove that specific listener when
there are multiple listeners bound to the same event name:
function listener1 {}
function listener2 {}
myInstance.on('started', listener1.bind(this));
myInstance.on('started', listener2.bind(this));
// Does nothing. listener2 remains bound.
myInstance.off('started', listener2);
Binding a this
context to a function creates a new instance of Function
, so when we
search for the listener function to remove it, the identity check fails:
function listener() {}
listener.bind(this) === listener.bind(this); // false
To use a custom this
context when binding a listener function, pass the context in the
call to on
instead:
function listener1 {}
function listener2 {}
myInstance.on('started', listener1, this);
myInstance.on('started', listener2, this);
To remove a specific listener and context combination, pass the listener and context to
off
:
// Removes listener2, but listener1 remains
myInstance.off('started', listener2, this);
// or:
myInstance.off('started'); // removes all listeners bound to 'started'
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:
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:
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);
}
We genuinely appreciate external contributions. See our extensive documentation on how to contribute.
This software is released under the MIT license. See the license file for more details.