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