UNPKG

@malagu/core

Version:
226 lines • 7.98 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.AsyncEmitter = exports.WaitUntilEvent = exports.Emitter = void 0; const event_1 = require("./event"); class Emitter { constructor(_options) { this._options = _options; this._disposed = false; this._leakWarnCountdown = 0; } /** * For the public to allow to subscribe * to events from this Emitter */ get event() { if (!this._event) { this._event = Object.assign((listener, thisArgs, disposables) => { if (!this._callbacks) { this._callbacks = new event_1.CallbackList(); } if (this._options && this._options.onFirstListenerAdd && this._callbacks.isEmpty()) { this._options.onFirstListenerAdd(this); } this._callbacks.add(listener, thisArgs); const removeMaxListenersCheck = this.checkMaxListeners(this._event.maxListeners); const result = { dispose: () => { if (removeMaxListenersCheck) { removeMaxListenersCheck(); } result.dispose = Emitter._noop; if (!this._disposed) { this._callbacks.remove(listener, thisArgs); result.dispose = Emitter._noop; if (this._options && this._options.onLastListenerRemove && this._callbacks.isEmpty()) { this._options.onLastListenerRemove(this); } } } }; if (Array.isArray(disposables)) { disposables.push(result); } return result; }, { maxListeners: Emitter.LEAK_WARNING_THRESHHOLD }); } return this._event; } checkMaxListeners(maxListeners) { if (maxListeners === 0 || !this._callbacks) { return undefined; } const listenerCount = this._callbacks.length; if (listenerCount <= maxListeners) { return undefined; } const popStack = this.pushLeakingStack(); this._leakWarnCountdown -= 1; if (this._leakWarnCountdown <= 0) { // only warn on first exceed and then every time the limit // is exceeded by 50% again this._leakWarnCountdown = maxListeners * 0.5; let topStack; let topCount = 0; this._leakingStacks.forEach((stackCount, stack) => { if (!topStack || topCount < stackCount) { topStack = stack; topCount = stackCount; } }); // eslint-disable-next-line max-len console.warn(`Possible Emitter memory leak detected. ${listenerCount} listeners added. Use event.maxListeners to increase the limit (${maxListeners}). MOST frequent listener (${topCount}):`); console.warn(topStack); } return popStack; } pushLeakingStack() { if (!this._leakingStacks) { this._leakingStacks = new Map(); } const stack = new Error().stack.split('\n').slice(3).join('\n'); const count = (this._leakingStacks.get(stack) || 0); this._leakingStacks.set(stack, count + 1); return () => this.popLeakingStack(stack); } popLeakingStack(stack) { if (!this._leakingStacks) { return; } const count = (this._leakingStacks.get(stack) || 0); this._leakingStacks.set(stack, count - 1); } /** * To be kept private to fire an event to * subscribers */ fire(event) { if (this._callbacks) { this._callbacks.invoke(event); } } /** * Process each listener one by one. * Return `false` to stop iterating over the listeners, `true` to continue. */ async sequence(processor) { if (this._callbacks) { for (const listener of this._callbacks) { if (!await processor(listener)) { break; } } } } dispose() { if (this._leakingStacks) { this._leakingStacks.clear(); this._leakingStacks = undefined; } if (this._callbacks) { this._callbacks.dispose(); this._callbacks = undefined; } this._disposed = true; } } exports.Emitter = Emitter; Emitter.LEAK_WARNING_THRESHHOLD = 175; Emitter._noop = function () { }; var WaitUntilEvent; (function (WaitUntilEvent) { /** * Fire all listeners in the same tick. * * Use `AsyncEmitter.fire` to fire listeners async one after another. */ async function fire(emitter, event, timeout = undefined) { const waitables = []; const asyncEvent = Object.assign(event, { waitUntil: (thenable) => { if (Object.isFrozen(waitables)) { throw new Error('waitUntil cannot be called asynchronously.'); } waitables.push(thenable); } }); try { emitter.fire(asyncEvent); // Asynchronous calls to `waitUntil` should fail. Object.freeze(waitables); } finally { delete asyncEvent['waitUntil']; } if (!waitables.length) { return; } if (timeout !== undefined) { await Promise.race([Promise.all(waitables), new Promise(resolve => setTimeout(resolve, timeout))]); } else { await Promise.all(waitables); } } WaitUntilEvent.fire = fire; })(WaitUntilEvent = exports.WaitUntilEvent || (exports.WaitUntilEvent = {})); const cancellation_1 = require("./cancellation"); class AsyncEmitter extends Emitter { /** * Fire listeners async one after another. */ fire(event, token = cancellation_1.CancellationToken.None, promiseJoin) { const callbacks = this._callbacks; if (!callbacks) { return Promise.resolve(); } const listeners = [...callbacks]; if (this.deliveryQueue) { return this.deliveryQueue = this.deliveryQueue.then(() => this.deliver(listeners, event, token, promiseJoin)); } return this.deliveryQueue = this.deliver(listeners, event, token, promiseJoin); } async deliver(listeners, event, token, promiseJoin) { for (const listener of listeners) { if (token.isCancellationRequested) { return; } const waitables = []; const asyncEvent = Object.assign(event, { waitUntil: (thenable) => { if (Object.isFrozen(waitables)) { throw new Error('waitUntil cannot be called asynchronously.'); } if (promiseJoin) { thenable = promiseJoin(thenable, listener); } waitables.push(thenable); } }); try { listener(event); // Asynchronous calls to `waitUntil` should fail. Object.freeze(waitables); } catch (e) { console.error(e); } finally { delete asyncEvent['waitUntil']; } if (!waitables.length) { return; } try { await Promise.all(waitables); } catch (e) { console.error(e); } } } } exports.AsyncEmitter = AsyncEmitter; //# sourceMappingURL=emitter.js.map