Silvermine Event Emitter
What is it?
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:
How do I use it?
Creating an event emitter
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() {};
Object.assign(MyClass.prototype, EventEmitterMixin);
_.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');
Basic Usage
Adding and removing event listeners
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');
});
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');
});
myInstance.emit('started');
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');
});
myInstance.emit('started');
myInstance.off('started');
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');
});
myInstance.off();
myInstance.emit('started');
myInstance.emit('ended');
Chaining
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')
Passing arguments with emitted events
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);
Advanced usage
Binding a listener function to multiple events
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);
myInstance.on('started', onChange)
.on('stopped', onChange)
.on('paused', onChange);
Emitting multiple events
You can emit multiple events with a single call to emit
by passing an array of event
names:
myInstance.emit([ 'initialized', 'started', 'played' ]);
Removing a single listener when multiple listeners are bound to the same event name
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);
myInstance.emit('started');
Using a custom this
context for a listener function
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));
myInstance.off('started', listener2);
Why does this happen?
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);
So how do I bind a this
context to a listener?
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
:
myInstance.off('started', listener2, this);
myInstance.off('started');
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:
const outerContext = this;
function registerListeners() {
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() {
myInstance.on('started', function() { console.log('Started', this.name); }, outerContext);
}
How do I contribute?
We genuinely appreciate external contributions. See our extensive
documentation on how to contribute.
License
This software is released under the MIT license. See the license file for more
details.