UNPKG

metautil

Version:
181 lines (154 loc) 4.37 kB
'use strict'; const DONE = { done: true, value: undefined }; class EventIterator { #resolvers = []; #emitter = null; #eventName = ''; #listener = null; #onerror = null; #done = false; constructor(emitter, eventName) { this.#emitter = emitter; this.#eventName = eventName; this.#listener = (value) => { for (const resolver of this.#resolvers) { resolver.resolve({ done: this.#done, value }); } }; emitter.on(eventName, this.#listener); this.#onerror = (error) => { for (const resolver of this.#resolvers) { resolver.reject(error); } this.#finalize(); }; emitter.on('error', this.#onerror); } next() { return new Promise((resolve, reject) => { if (this.#done) return void resolve(DONE); this.#resolvers.push({ resolve, reject }); }); } #finalize() { if (this.#done) return; this.#done = true; this.#emitter.off(this.#eventName, this.#listener); this.#emitter.off('error', this.#onerror); for (const resolver of this.#resolvers) { resolver.resolve(DONE); } this.#resolvers.length = 0; } async return() { this.#finalize(); return DONE; } async throw() { this.#finalize(); return DONE; } } class EventIterable { #emitter = null; #eventName = ''; constructor(emitter, eventName) { this.#emitter = emitter; this.#eventName = eventName; } [Symbol.asyncIterator]() { return new EventIterator(this.#emitter, this.#eventName); } } class Emitter { #events = new Map(); #maxListeners = 10; constructor(options = {}) { this.#maxListeners = options.maxListeners ?? 10; } emit(eventName, value) { const event = this.#events.get(eventName); if (!event) { if (eventName !== 'error') return Promise.resolve(); throw new Error('Unhandled error'); } const on = event.on.slice(); const promises = on.map(async (fn) => fn(value)); if (event.once.size > 0) { const len = event.on.length; const on = new Array(len); let index = 0; for (let i = 0; i < len; i++) { const listener = event.on[i]; if (!event.once.has(listener)) on[index++] = listener; } if (index === 0) { this.#events.delete(eventName); return Promise.resolve(); } on.length = index; this.#events.set(eventName, { on, once: new Set() }); } return Promise.all(promises).then(() => undefined); } #addListener(eventName, listener, once) { let event = this.#events.get(eventName); if (!event) { const on = [listener]; event = { on, once: once ? new Set(on) : new Set() }; this.#events.set(eventName, event); } else { if (event.on.includes(listener)) { throw new Error('Duplicate listeners detected'); } event.on.push(listener); if (once) event.once.add(listener); } if (event.on.length > this.#maxListeners) { throw new Error( `MaxListenersExceededWarning: Possible memory leak. ` + `Current maxListeners is ${this.#maxListeners}.`, ); } } on(eventName, listener) { this.#addListener(eventName, listener, false); } once(eventName, listener) { this.#addListener(eventName, listener, true); } off(eventName, listener) { if (!listener) return void this.#events.delete(eventName); const event = this.#events.get(eventName); if (!event) return; const index = event.on.indexOf(listener); if (index > -1) event.on.splice(index, 1); event.once.delete(listener); } toPromise(eventName) { return new Promise((resolve) => { this.once(eventName, resolve); }); } toAsyncIterable(eventName) { return new EventIterable(this, eventName); } clear(eventName) { if (!eventName) return void this.#events.clear(); this.#events.delete(eventName); } listeners(eventName) { if (!eventName) throw new Error('Expected eventName'); const event = this.#events.get(eventName); return event ? event.on : []; } listenerCount(eventName) { if (!eventName) throw new Error('Expected eventName'); const event = this.#events.get(eventName); return event ? event.on.length : 0; } eventNames() { return Array.from(this.#events.keys()); } } module.exports = { Emitter };