Skip to content

Instantly share code, notes, and snippets.

@brunocalou
Created July 10, 2018 18:40
Show Gist options
  • Save brunocalou/858349e1ad893a08bb2af667d576dc79 to your computer and use it in GitHub Desktop.
Save brunocalou/858349e1ad893a08bb2af667d576dc79 to your computer and use it in GitHub Desktop.
A simple yet powerful event system capable of dispatching events with any number of arguments
/**
* A simple yet powerful event system capable of dispatching events with any number of arguments
*
* @example
* const eventSystem = new EventSystem();
* eventSystem.on('my-event', () => console.log('hello event system'));
* eventSystem.on('my-event', () => console.log('hello from here too'));
*
* // In the future
* eventSystem.dispatch('my-event');
*
* // Output
* // hello event system
* // hello from here too
*
* @example
* const eventSystem = new EventSystem();
* eventSystem.on('my-event-with-arguments', (arg1, arg2, arg3, arg4) => {
* console.log('hello event system');
* console.log(arg1);
* console.log(arg2);
* console.log(arg3);
* console.log(arg4);
* });
*
* // In the future
* eventSystem.dispatch('my-event-with-arguments', 'first argument', 2, { argument: 3 }, [4]);
*
* // Output
* // first argument
* // 2
* // Object { argument: 3 }
* // Array [ 4 ]
*
* @example
* class Coffee {
* constructor({
* flavor,
* quantity,
* }) {
* this.flavor = flavor;
* this.quantity = quantity;
* }
*
* drink() {
* this.quantity = 0;
* console.log('Yummy!');
* }
* }
*
* class CoffeeMachine extends EventSystem {
* makeCoffee() {
* setTimeout(() => this.dispatch('coffee-ready', new Coffee('vanilla', 100), 1000));
* }
* }
*
* const machine = new CoffeeMachine();
* machine.on('coffee-ready', coffee => coffee.drink());
* machine.makeCoffee();
*
* // Output
* // Yummy!
*/
export default class EventSystem {
constructor() {
this.events = {};
}
/**
* Listens to an event
* @param {string} event The event to listen to
* @param {function} callback The function to call when the event is dispatched
* @param {object} context The context to use on the callback call
*/
on(event, callback, context) {
if (this.events[event] === undefined) {
this.events[event] = [];
}
this.events[event].push({
callback,
context,
});
}
/**
* Removes a callback from an event. If the callback is undefined, all the callbacks for the
* specified event will be removed
*
* @example
* const eventSystem = new EventSystem();
* const callback = () => console.log('callback was called');
*
* eventSystem.on('my-event', callback);
* eventSystem.off('my-event', callback);
* eventSystem.dispatch('my-event');
* // No Output
*
* @example
* const eventSystem = new EventSystem();
* const callback = () => console.log('callback was called');
* const anotherCallback = () => console.log('another callback was called');
*
* eventSystem.on('my-event', callback);
* eventSystem.on('my-event', anotherCallback);
* eventSystem.off('my-event');
* eventSystem.dispatch('my-event');
* // No Output
*
* @param {string} event The event
* @param {function} callback The callback to be removed
*/
off(event, callback) {
if (this.events[event] !== undefined) {
if (callback !== undefined) {
this.events[event] = this.events[event].filter(listener => listener.callback !== callback);
} else {
delete this.events[event];
}
}
}
/**
* Dispatches an event
* @param {string} event The event to be dispatched
* @param {any} args The arguments to dispatch
*/
dispatch(event, ...args) {
const listeners = this.events[event];
if (listeners !== undefined) {
listeners.forEach(listener => listener.callback.call(listener.context, ...args));
}
}
}
@brunocalou
Copy link
Author

Typescript version

/**
 * A simple yet powerful event system capable of dispatching events with any number of arguments
 *
 * @example
 * const eventSystem = new EventSystem();
 * eventSystem.on('my-event', () => console.log('hello event system'));
 * eventSystem.on('my-event', () => console.log('hello from here too'));
 *
 * // In the future
 * eventSystem.dispatch('my-event');
 *
 * // Output
 * // hello event system
 * // hello from here too
 *
 * @example
 * const eventSystem = new EventSystem();
 * eventSystem.on('my-event-with-arguments', (arg1, arg2, arg3, arg4) => {
 *   console.log('hello event system');
 *   console.log(arg1);
 *   console.log(arg2);
 *   console.log(arg3);
 *   console.log(arg4);
 * });
 *
 * // In the future
 * eventSystem.dispatch('my-event-with-arguments', 'first argument', 2, { argument: 3 }, [4]);
 *
 * // Output
 * // first argument
 * // 2
 * // Object { argument: 3 }
 * // Array [ 4 ]
 *
 * @example
 * class Coffee {
 *   constructor({
 *     flavor,
 *     quantity,
 *   }) {
 *     this.flavor = flavor;
 *     this.quantity = quantity;
 *   }
 *
 *   drink() {
 *     this.quantity = 0;
 *     console.log('Yummy!');
 *   }
 * }
 *
 * class CoffeeMachine extends EventSystem {
 *   makeCoffee() {
 *     setTimeout(() => this.dispatch('coffee-ready', new Coffee('vanilla', 100), 1000));
 *   }
 * }
 *
 * const machine = new CoffeeMachine();
 * machine.on('coffee-ready', coffee => coffee.drink());
 * machine.makeCoffee();
 *
 * // Output
 * // Yummy!
 */
export default class EventSystem {
  events: Map<String, IEventHolder[]>

  constructor() {
    this.events = new Map();
  }

  /**
   * Listens to an event
   * @param {string} event The event to listen to
   * @param {Callback} callback The function to call when the event is dispatched
   * @param {object} context The context to use on the callback call
   */
  on(event: string, callback: Callback, context?: object) {
    if (!this.events.has(event)) {
      this.events.set(event, []);
    }

    const eventList = this.events.get(event)
    if (eventList) {
      eventList.push({
        callback,
        context,
      });
    }
  }

  /**
   * Removes a callback from an event. If the callback is undefined, all the callbacks for the
   * specified event will be removed
   *
   * @example
   * const eventSystem = new EventSystem();
   * const callback = () => console.log('callback was called');
   *
   * eventSystem.on('my-event', callback);
   * eventSystem.off('my-event', callback);
   * eventSystem.dispatch('my-event');
   * // No Output
   *
   * @example
   * const eventSystem = new EventSystem();
   * const callback = () => console.log('callback was called');
   * const anotherCallback = () => console.log('another callback was called');
   *
   * eventSystem.on('my-event', callback);
   * eventSystem.on('my-event', anotherCallback);
   * eventSystem.off('my-event');
   * eventSystem.dispatch('my-event');
   * // No Output
   *
   * @param {string} event The event
   * @param {Callback} callback The callback to be removed
   */
  off(event: string, callback?: Callback) {
    if (this.events.has(event)) {
      if (callback !== undefined) {
        const eventList = (this.events.get(event) || []).filter(listener => listener.callback !== callback)
        this.events.set(event, eventList);
      } else {
        this.events.delete(event)
      }
    }
  }

  /**
   * Dispatches an event
   * @param {string} event The event to be dispatched
   * @param {any} args The arguments to dispatch
   */
  dispatch(event: String, ...args: any[]) {
    const listeners = this.events.get(event);
    if (listeners !== undefined) {
      listeners.forEach(listener => listener.callback.call(listener.context, ...args));
    }
  }
}

type Callback = (...args: any) => any

interface IEventHolder {
  callback: Callback
  context?: object
}

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