-
-
Save mudge/5830382 to your computer and use it in GitHub Desktop.
/* Polyfill indexOf. */ | |
var indexOf; | |
if (typeof Array.prototype.indexOf === 'function') { | |
indexOf = function (haystack, needle) { | |
return haystack.indexOf(needle); | |
}; | |
} else { | |
indexOf = function (haystack, needle) { | |
var i = 0, length = haystack.length, idx = -1, found = false; | |
while (i < length && !found) { | |
if (haystack[i] === needle) { | |
idx = i; | |
found = true; | |
} | |
i++; | |
} | |
return idx; | |
}; | |
}; | |
/* Polyfill EventEmitter. */ | |
var EventEmitter = function () { | |
this.events = {}; | |
}; | |
EventEmitter.prototype.on = function (event, listener) { | |
if (typeof this.events[event] !== 'object') { | |
this.events[event] = []; | |
} | |
this.events[event].push(listener); | |
}; | |
EventEmitter.prototype.removeListener = function (event, listener) { | |
var idx; | |
if (typeof this.events[event] === 'object') { | |
idx = indexOf(this.events[event], listener); | |
if (idx > -1) { | |
this.events[event].splice(idx, 1); | |
} | |
} | |
}; | |
EventEmitter.prototype.emit = function (event) { | |
var i, listeners, length, args = [].slice.call(arguments, 1); | |
if (typeof this.events[event] === 'object') { | |
listeners = this.events[event].slice(); | |
length = listeners.length; | |
for (i = 0; i < length; i++) { | |
listeners[i].apply(this, args); | |
} | |
} | |
}; | |
EventEmitter.prototype.once = function (event, listener) { | |
this.on(event, function g () { | |
this.removeListener(event, g); | |
listener.apply(this, arguments); | |
}); | |
}; |
jonathanbsilva
commented
Jul 31, 2020
You might want to check this repo in order to find out how to create simple type safe event emitter library in Typescript.
Come from Pramp. Spent a lot of time figuring out this problem. There is an easier version without once implemented.
Two different ways to implement on and once, be aware of the difference.
class EventEmitter {
constructor() {
this.events = {};
}
on(event, listener) {
if (!(event in this.events)) {
this.events[event] = [];
}
this.events[event].push(listener);
return () => this.removeListener(event, listener);
}
removeListener(event, listener) {
if (!(event in this.events)) {
return;
}
const idx = this.events[event].indexOf(listener);
if (idx > -1) {
this.events[event].splice(idx, 1);
}
if (this.events[event].length === 0) {
delete this.events[event];
}
}
emit(event, ...args) {
if (!(event in this.events)) {
return;
}
this.events[event].forEach(listener => listener(...args));
}
once(event, listener) {
const remove = this.on(event, (...args) => {
remove();
listener(...args);
});
}
};
Second way:
on(event, listener) {
if (!(event in this.events)) {
this.events[event] = [];
}
this.events[event].push(listener);
// return () => this.removeListener(event, listener);
}
once(event, listener) {
const self = this;
this.on(event, function onceFn(...args) {
self.removeListener(event, onceFn);
listener(...args);
});
}
- Modified .on() to add the elements to the start of the array (slower).
- Modified .emit() to loop backwards through the events (much faster). This also avoids the race condition in the relationship between .once() and .emit() as noted by @undecidedapollo up there.
- Modified .removeAllListeners() to take an optional argument to allow the removal of all events associated with a specific name.
In a nutshell...
EventEmitter.prototype.on = function on( name, fn ) {
this.events[name] = [fn].concat( this.events[name] || [] );
}
EventEmitter.prototype.emit = function emit( name, data ) {
for ( let i = this.events[name].length - 1; i >= 0 ; --i ) {
this.events[name][i]( data );
}
}
EventEmitter.prototype.removeAllListeners = function removeAllListeners( name ) {
if ( name ) {
delete this.events[name];
} else {
// drop the old reference
this.events = {};
}
}
You can add simple overrides by returning true or false to stop propagation, e.g. return true to stop all other events before it.
// hooks.js
// inspired by gmod lua !
// it is useful to prevent circular dependencies and or import hell
export class hooks {
static register(name, f) {
if (!hooks[name])
hooks[name] = [];
hooks[name].push(f);
return f;
}
static unregister(name, f) {
hooks[name] = hooks[name].filter(e => e != f);
}
static call(name, x) {
if (!hooks[name])
return;
for (let i = hooks[name].length; i--;)
if (hooks[name][i](x))
return;
}
}
export default hooks;
Hello, this is actually amazing. What license do you release this under?
This was extracted from my Promise library Pacta and, as such, is released under the BSD 3-clause license.
@undecidedapollo and @coldsilk:
There seems to be a problem with the once/removeListener function. When the event is emitted and the listener is called, it calls remove. The remove function splices the array stored at this.event[eventString] while the emit function is doing a forEach on the same array. This splice modifies the array while it is being iterated against, and causes the forEach to skip the next listener.
It has been a while since I wrote this but I believe this is why emit
takes a shallow copy of the list of listeners on line 55 using slice
so that the loop is unaffected by removeListener
modifying the underlying events.