Skip to content

Instantly share code, notes, and snippets.

@mgtitimoli
Last active March 23, 2017 20:28
Show Gist options
  • Save mgtitimoli/94120c4389589ffa708a374e0b8a788b to your computer and use it in GitHub Desktop.
Save mgtitimoli/94120c4389589ffa708a374e0b8a788b to your computer and use it in GitHub Desktop.
Yet Another EventEmitter
// @flow
import mapValues from 'lodash/fp/mapValues';
type Config = {
allowed?: Array<string>
};
type ListenerRelatedMethod = (eventName: string, listener: Function) => void;
type EventEmitter = {
emit: (eventName: string, ...args: Array<any>) => void;
on: ListenerRelatedMethod,
once: ListenerRelatedMethod,
off: ListenerRelatedMethod,
offAll: (eventName: string) => void,
};
type Properties = {
config: Config,
listeners: Map<string, Array<Function>>,
};
type Methods = {
emit: Properties => $PropertyType<EventEmitter, 'emit'>,
on: Properties => $PropertyType<EventEmitter, 'on'>,
once: Properties => $PropertyType<EventEmitter, 'once'>,
off: Properties => $PropertyType<EventEmitter, 'off'>,
offAll: Properties => $PropertyType<EventEmitter, 'offAll'>,
}
type MethodRunner = (
eventEmitter: Properties,
eventName: string,
...args: Array<any>
) => void;
// We need this as Flow is not detecting the pattern has(key) && get(key)
const getEventListeners = (listeners, eventName) =>
listeners.get(eventName) || [];
const isEventListened = (listeners, eventName) =>
listeners.has(eventName)
&& getEventListeners(listeners, eventName).length !== 0;
const addListener = (
{listeners}: Properties,
eventName: string,
listener: Function
) => {
if (!listeners.has(eventName)) {
listeners.set(eventName, []);
}
getEventListeners(listeners, eventName).push(listener);
};
const removeListener = (
{listeners}: Properties,
eventName: string,
listener: Function
) => {
const eventListeners = getEventListeners(listeners, eventName);
eventListeners.splice(eventListeners.indexOf(listener), 1);
};
const ensureEventAllowed = (allowed, eventName) => {
if (allowed && !allowed.includes(eventName)) {
throw new Error('Event not allowed: ' + eventName);
}
};
const createMethod = (run: MethodRunner) => (eventEmitter: Properties) =>
(eventName: string, ...args: Array<any>) => {
ensureEventAllowed(eventEmitter.config.allowed, eventName);
run(eventEmitter, eventName, ...args);
};
const createIfEventListenedMethod = run =>
createMethod((eventEmitter, eventName, ...args) => {
if (isEventListened(eventEmitter.listeners, eventName)) {
run(eventEmitter, eventName, ...args);
}
});
const emit = createIfEventListenedMethod(({listeners}, eventName, ...args) => {
getEventListeners(listeners, eventName).forEach(
listener => listener(...args)
);
});
const on = createMethod(addListener);
const once = createMethod((
eventEmitter: Properties,
eventName: string,
listener: Function
) => {
const onceListener = (...args) => {
listener(...args);
removeListener(eventEmitter, eventName, onceListener);
};
addListener(eventEmitter, eventName, onceListener);
});
const off = createIfEventListenedMethod(removeListener);
const offAll = createIfEventListenedMethod(({listeners}, eventName) => {
listeners.set(eventName, []);
});
const methods: Methods = {
emit,
on,
once,
off,
offAll,
};
const createEventEmitter = (config: Config = {}): EventEmitter => {
const instance = {config, listeners: new Map()};
return mapValues(methodOf => methodOf(instance), methods);
};
export default createEventEmitter;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment