Skip to content

Instantly share code, notes, and snippets.

@ZeroDragon
Last active February 20, 2020 23:02
Show Gist options
  • Save ZeroDragon/1729ea65dd434dee59d6afdd2f42a1ef to your computer and use it in GitHub Desktop.
Save ZeroDragon/1729ea65dd434dee59d6afdd2f42a1ef to your computer and use it in GitHub Desktop.
Browser side eventEmitter
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
Copy link
Author

image

@narcisso
Copy link

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); });
};

@narcisso
Copy link

// 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

@ZeroDragon
Copy link
Author

Ok aqui ve va la versión mejorada... quedó gordito pero en el ejemplo que pusiste veo 2 cosas que quise mejorar:

  1. Para hacer un off necesitas mandarle la misma función para poder des suscribirte, así que cambié eso a un uuid
  2. 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')

resultado:
image

@narcisso
Copy link

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 ...

@narcisso
Copy link

narcisso commented Feb 20, 2020

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.

@ZeroDragon
Copy link
Author

flexeas bien cabrón, dude. Tengo q mejorar mucho

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment