UNPKG

als-event-emitter

Version:

A powerful, asynchronous, promise-based event emitter with support for chaining and advanced event handling.

193 lines (170 loc) 6.96 kB
const EventEmitter = (function(){ class SmartPromise { static counter = 0; static chain = new Map(); static use(fn, { once = false, last = false } = {}, map = this.chain) { if (typeof fn !== 'function') throw new Error('Item must be a function'); map.set(fn, { once, last, order: this.counter++, any: map === this.chain }); } constructor(timeout = null,name) { this.name = name this.chain = new Map(); this.timeout = timeout; this.linked = new Set() this.async = false } use(fn, options = {}) { SmartPromise.use(fn, options, this.chain); } delete(fn) { this.chain.delete(fn); SmartPromise.chain.delete(fn); } link(chain) { if (chain instanceof SmartPromise) this.linked.add(chain) } getLinked(visited = new Set(), linked = new Set()) { if (visited.has(this.chain)) return visited.add(this.chain) Array.from(this.linked).forEach(l => { if (visited.has(l)) return visited.add(l) l.getLinked(visited, linked) l.chain.entries().forEach(item => linked.add(item)) }) return { linked, visited } } getFns() { let { linked, visited } = this.getLinked() linked = Array.from(linked); visited = Array.from(visited) const entries = [...linked, ...SmartPromise.chain.entries(), ...this.chain.entries()]; const lastFns = []; const fns = entries .sort((a, b) => a[1].order - b[1].order) .map(([fn, { once, any, last }]) => { if (fn.constructor.name === 'AsyncFunction') this.async = true if (once) { if (any) SmartPromise.chain.delete(fn); else visited.forEach(chain => chain.delete(fn)) } if (last) { lastFns.push(fn); return false; } return fn; }).filter(Boolean); return Array.from(new Set([...fns, ...lastFns])); } execute(...args) { this.async = false; const fns = this.getFns(); return this.async ? this.executeAsync(fns, ...args) : this.executeSync(fns, ...args) } executeSync(fns, ...args) { if (fns.length === 0) return; let result const res = (r) => { result = r }, rej = (error) => { throw error } const { resolve, reject, isResolved } = this.getResolved(res, rej) const callNext = (index, result) => { if (index >= fns.length) return resolve(result) const next = (result) => callNext(index + 1, result); try { const fn = fns[index] fn({ next, resolve, reject, result, name: this.name }, ...args); if (index === fns.length - 1 && !isResolved()) resolve(result); } catch (error) { reject(error); } }; callNext(0); return result } executeAsync(fns, ...args) { if (fns.length === 0) return Promise.resolve(); return new Promise((res, rej) => { const { resolve, reject, isResolved } = this.getResolved(res, rej) const callNext = async (index, result) => { if (index >= fns.length) return resolve(result) const next = (result) => callNext(index + 1, result); try { const fn = fns[index] await fn({ next, resolve, reject, result, name: this.name }, ...args); if (index === fns.length - 1 && !isResolved()) resolve(result); } catch (error) { reject(error); } }; callNext(0); }); } getResolved(res, rej) { const { timeout } = this let timer = null, resolved = false; const finish = (fn, param) => { if (resolved) return if (timer) { clearTimeout(timer); timer = null; } resolved = true; fn(param) } const resolve = (result) => { finish(res, result) } const reject = (error) => { finish(rej, error) } if (timeout) timer = setTimeout(() => { reject(new Error(`Promise timeout ${timeout}`)) }, timeout); const isResolved = () => resolved return { resolve, reject, isResolved } } } class EventEmitter { static once(listener) { SmartPromise.use(listener, { once: true, last:false }) } static onLast(listener) { SmartPromise.use(listener, { once: false, last:true }) } static onceLast(listener) { SmartPromise.use(listener, { once: true, last:true }) } static on(listener, options = {}) { SmartPromise.use(listener,options) } static removeAllListeners() {SmartPromise.chain.clear()} constructor(chain = true,timeout = null) { this.chain = chain this.timeout = timeout this.removeAllListeners() } removeAllListeners() { this.listeners = {} this.anyChain = new SmartPromise() return this } getChain(eventName, group) { if (!this.listeners[eventName]) { this.listeners[eventName] = new SmartPromise(this.timeout,eventName) this.listeners[eventName].link(this.anyChain) if(this.listeners[group]) this.listeners[eventName].link(this.listeners[group]) } return this.listeners[eventName] } has(eventName) { return !!this.listeners[eventName] && this.listeners[eventName].chain.size > 0 } off(eventName) { delete this.listeners[eventName]; return this } on(eventName, listener, options = {}) { const { once = false, last = false, group } = options const chain = this.getChain(eventName, group) chain.use(listener, { once, last }) return this } removeListener(listener) { this.anyChain.delete(listener) for (let eventName in this.listeners) { this.listeners[eventName].chain.delete(listener) } return this; } onAny(listener, options = {}) { const { once = false, last = false } = options this.anyChain.use(listener, { once, last }) return this; } emit(eventName, ...args) { const chain = this.listeners[eventName] || this.anyChain; if (this.chain) return chain.execute(...args) const fns = chain.getFns() fns.forEach(fn => fn(...args)); } once(eventName, listener, group) { return this.on(eventName, listener, { once: true, group }) } onLast(eventName, listener, group) { return this.on(eventName, listener, { once: false, last: true, group }) } onceLast(eventName, listener, group) { return this.on(eventName, listener, { once: true, last: true, group }) } onceAnyLast(listener) { return this.onAny(listener, { once: true, last: true }) } onAnyLast(listener) { return this.onAny(listener, { once: false, last: true }) } onceAny(listener) { return this.onAny(listener, { once: true }) } } return EventEmitter })();