-
-
Save ZeroDragon/1729ea65dd434dee59d6afdd2f42a1ef to your computer and use it in GitHub Desktop.
class EventEmitter { | |
constructor () { | |
this.DOM = document.createElement("p") | |
} | |
on(channel, fn) { | |
this.DOM.addEventListener(channel, event => { | |
fn(event.detail) | |
}) | |
} | |
dispatch(channel, data) { | |
const detail = typeof data === 'string' ? data : JSON.stringify(data) | |
this.DOM.dispatchEvent(new CustomEvent(channel, { detail })) | |
} | |
} | |
// Usage | |
const myEvent = new EventEmitter() | |
myEvent.on('message', console.log) | |
myEvent.dispatch('message', 'hi browser side events') // "hi browser side events" |
ZeroDragon
commented
Jan 21, 2020
const Events = { }; // Internal
const EventBus = { }; // External
EventBus.on = function(eventName, handler){
Events[eventName] || (Events[eventName] = []);
Events[eventName].push(handler);
};
EventBus.once = function(eventName, handler){
handler.once = true;
EventBus.on(eventName, handler);
};
EventBus.off = function(eventName, handler){
if(!Events[eventName] || !Events[eventName].length){ return false; }
if(typeof handler !== 'function'){ return Events[eventName] = []; }
let index = Events[eventName].indexOf(handler);
if(index !== -1){ Events[eventName].splice(index, 1); }
};
EventBus.emit = function(eventName, data){
if(!Events[eventName] || !Events[eventName].length){ return false; }
let once = [];
Events[eventName].forEach( (eventHandler, index) => {
if(eventHandler.once){ once.push(index); }
try{
eventHandler(data);
}catch(error){
console.error(error);
}
});
once.forEach( index =>{ Events[eventName].splice(index, 1); });
};
// Usage
function myActionHandler(action){
console.log(action);
}
EventBus.on('Namespace/Entity:action', myActionHandler); // Subscribe myActionHandler to Event
EventBus.emit('Namespace/Entity:action', 1); // Emited
EventBus.emit('Namespace/Entity:action', 2); // Emited
EventBus.off('Namespace/Entity:action', myActionHandler); // Unsubscribe from event
EventBus.emit('Namespace/Entity:action', 3); // Not emited to myActionHandler
Ok aqui ve va la versión mejorada... quedó gordito pero en el ejemplo que pusiste veo 2 cosas que quise mejorar:
- Para hacer un
off
necesitas mandarle la misma función para poder des suscribirte, así que cambié eso a un uuid - al agregarle el .once a una handler que mandas, como la fn pasa referenciada, en realidad mutas la función que le mandas en el .once, así que metí la función dentro de un closure y para que no lo haga 2 veces, le puse una banderita de eventbus = true, así cuando mandas a llamar el .once, o el .on independientemente, no lo hace 2 veces
class Eventbus {
constructor () {
this.events = {}
}
_getEvent (eventName, strict) {
const { events } = this
if (strict && !events[eventName]) return false
return events[eventName] || (events[eventName] = [])
}
_getUUID () {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8)
return v.toString(16)
})
}
_createHandler (handler) {
const _handler = (...params) => handler(...params)
_handler.__eventBus = true
_handler.__uuid = this._getUUID()
return _handler
}
_getHandler (eventName, uid) {
return this._getEvent(eventName).find(event => event.__uuid === uid)
}
once (eventName, handler) {
const _handler = this._createHandler(handler)
_handler.__once = true
return this.on(eventName, _handler)
}
on (eventName, handler) {
const _handler = handler.__eventBus ? handler : this._createHandler(handler)
const event = this._getEvent(eventName)
event.push(_handler)
return _handler.__uuid
}
off (eventName, uuid) {
const event = this._getEvent(eventName)
if (!event.length) return
this.events[eventName] = event.filter(_handler => _handler.__uuid !== uuid)
}
emit (eventName, data) {
const event = this._getEvent(eventName, true)
if (!event) return event
this.events[eventName] = event.filter(handler => {
try { handler(data) }
catch (error) { console.error(error) }
return !handler.__once
})
}
}
Modo de uso:
const event = new Eventbus
let eventID = event.on('Namespace/Entity:action', console.log)
event.emit('Namespace/Entity:action', 'este mensaje debe llegar 1')
event.emit('Namespace/Entity:action', 'este mensaje debe llegar 2')
event.off('Namespace/Entity:action', eventID)
event.emit('Namespace/Entity:action', 'este mensaje NO debe llegar')
eventID = event.once('Namespace/Entity:action', console.log)
event.emit('Namespace/Entity:action', 'este mensaje debe llegar 3')
event.emit('Namespace/Entity:action', 'este mensaje NO debe llegar')
eventID = event.on('Namespace/Entity:action', console.log)
event.emit('Namespace/Entity:action', 'este mensaje debe llegar 4')
event.emit('Namespace/Entity:action', 'este mensaje debe llegar 5')
El mandar la funcion para el off
como segundo argument es un "standard" para Event Based libraries, ejemplo
DOM : https://www.w3schools.com/jsref/met_element_removeeventlistener.asp
nodeJS emitter : https://nodejs.org/api/events.html#events_emitter_removelistener_eventname_listener
jQuery : https://api.jquery.com/off/#off-events-selector-handler
Internals http en node : https://github.com/nodejs/node/blob/master/lib/_http_server.js#L630-L635
... y otros
yo sugiero usar el mismo patron ...
si no me equiboco hay memory leak en el once
ya que solo hay un filter pero no purga el handler del event queue
en nodos dinamicos esto puede ser critico.
flexeas bien cabrón, dude. Tengo q mejorar mucho