Skip to content

Instantly share code, notes, and snippets.

@grncdr
Created February 18, 2014 02:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save grncdr/9063660 to your computer and use it in GitHub Desktop.
Save grncdr/9063660 to your computer and use it in GitHub Desktop.
Minimal listener/event notifier for JavaScript

event-emitter-minus

EventEmitter without a base class or string event names. Supports adding, removing, and one-shot listeners.

Synopsis

var listeners = require('listeners')

var onEvent = listeners()

onEvent(function (time) {
  console.log("the time is", time)
})

onEvent.once(function (arg1, arg2) {
  console.log('the time is', time, '[once]')
})

setInterval(function () {
  onEvent.notify(new Date())
}, 1000)

Rationale

Node's built-in EventEmitter allows you to name events using arbitrary strings. While this is convenient for module authors, it presents great difficulty for module readers: humans must search documentation (or module source code) to learn the names of events and the data they might emit, and static-analysis tools are unable to determine whether a given event name is valid or not.

Furthermore, the flexibility afforded by emit(eventName) is rarely used in practice, with most event emitters using only a small set of constant event names (e.g. 'data', 'error' and 'end').

This module implements the core functionality of EventEmitter --maintaining a list of callbacks to be called when a given event is triggered-- using higher-order functions and closures rather than a base class and collection of methods. Favouring composition over inheritance, EventEmitter- instances can be attached to an object using statically analyzable names.

Consider this simple Counter example:

inherits(Counter, EventEmitter)
function Counter (value) {
  EventEmitter.call(this)
  this.value = value || 0
}

Counter.prototype.inc = function () {
  this.emit('change', ++this.value)
}

Counter.prototype.dec = function () {
  this.emit('change', --this.value)
}

The above EventEmitter instance only ever emits one event: 'change'. We can instead write this using EventEmitter- to be:

function Counter (value) {
  this.onChange = eeMinus()
  this.value = value || 0
}

Counter.prototype.inc = function () {
  this.onChange.notify(++this.value)
}

Counter.prototype.dec = function () {
  this.onChange.notify(--this.value)
}

The benefit here is that we now have a well defined interface for static analysis and documentation tools to look at.

module.exports = function () {
var callbacks = []
function addCallback (callback) {
callbacks.push(callback)
}
addCallback.once = function (callback) {
wrapper.callback = callback
addCallback(wrapper)
function wrapper () {
addCallback.remove(wrapper)
return callback.apply(this, arguments)
}
}
addCallback.remove = function (callback) {
callbacks.filter(function (it) {
if (typeof it.callback === 'function') {
return it.callback !== callback
}
return it !== callback
})
}
addCallback.removeAll = function () {
callbacks = []
}
addCallback.notify = function () {
var len = callbacks.length
var i = 0
while (i < len) {
if (callbacks[i].apply(this, arguments) === false) {
break;
}
if (callbacks.length < len) {
len = callbacks.length
} else {
i++
}
}
}
return addCallback
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment