UNPKG

acebase-core

Version:

Shared AceBase core components, no need to install manually

186 lines 7.97 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.EventStream = exports.EventPublisher = exports.EventSubscription = void 0; class EventSubscription { /** * @param stop function that stops the subscription from receiving future events */ constructor(stop) { this.stop = stop; this._internal = { state: 'init', activatePromises: [], }; } /** * Notifies when subscription is activated or canceled * @param callback optional callback to run each time activation state changes * @returns returns a promise that resolves once activated, or rejects when it is denied (and no callback was supplied) */ activated(callback) { if (callback) { this._internal.activatePromises.push({ callback }); if (this._internal.state === 'active') { callback(true); } else if (this._internal.state === 'canceled') { callback(false, this._internal.cancelReason); } } // Changed behaviour: now also returns a Promise when the callback is used. // This allows for 1 activated call to both handle: first activation result, // and any future events using the callback return new Promise((resolve, reject) => { if (this._internal.state === 'active') { return resolve(); } else if (this._internal.state === 'canceled' && !callback) { return reject(new Error(this._internal.cancelReason)); } // eslint-disable-next-line @typescript-eslint/no-empty-function const noop = () => { }; this._internal.activatePromises.push({ resolve, reject: callback ? noop : reject, // Don't reject when callback is used: let callback handle this (prevents UnhandledPromiseRejection if only callback is used) }); }); } /** (for internal use) */ _setActivationState(activated, cancelReason) { this._internal.cancelReason = cancelReason; this._internal.state = activated ? 'active' : 'canceled'; while (this._internal.activatePromises.length > 0) { const p = this._internal.activatePromises.shift(); if (activated) { p.callback && p.callback(true); p.resolve && p.resolve(); } else { p.callback && p.callback(false, cancelReason); p.reject && p.reject(cancelReason); } } } } exports.EventSubscription = EventSubscription; class EventPublisher { /** * * @param publish function that publishes a new value to subscribers, return if there are any active subscribers * @param start function that notifies subscribers their subscription is activated * @param cancel function that notifies subscribers their subscription has been canceled, removes all subscriptions */ constructor(publish, start, cancel) { this.publish = publish; this.start = start; this.cancel = cancel; } } exports.EventPublisher = EventPublisher; class EventStream { constructor(eventPublisherCallback) { const subscribers = []; let noMoreSubscribersCallback; let activationState; // TODO: refactor to string only: STATE_INIT, STATE_STOPPED, STATE_ACTIVATED, STATE_CANCELED const STATE_STOPPED = 'stopped (no more subscribers)'; this.subscribe = (callback, activationCallback) => { if (typeof callback !== 'function') { throw new TypeError('callback must be a function'); } else if (activationState === STATE_STOPPED) { throw new Error('stream can\'t be used anymore because all subscribers were stopped'); } const sub = { callback, activationCallback: function (activated, cancelReason) { activationCallback === null || activationCallback === void 0 ? void 0 : activationCallback(activated, cancelReason); this.subscription._setActivationState(activated, cancelReason); }, subscription: new EventSubscription(function stop() { subscribers.splice(subscribers.indexOf(this), 1); return checkActiveSubscribers(); }), }; subscribers.push(sub); if (typeof activationState !== 'undefined') { if (activationState === true) { activationCallback === null || activationCallback === void 0 ? void 0 : activationCallback(true); sub.subscription._setActivationState(true); } else if (typeof activationState === 'string') { activationCallback === null || activationCallback === void 0 ? void 0 : activationCallback(false, activationState); sub.subscription._setActivationState(false, activationState); } } return sub.subscription; }; const checkActiveSubscribers = () => { let ret; if (subscribers.length === 0) { ret = noMoreSubscribersCallback === null || noMoreSubscribersCallback === void 0 ? void 0 : noMoreSubscribersCallback(); activationState = STATE_STOPPED; } return Promise.resolve(ret); }; this.unsubscribe = (callback) => { const remove = callback ? subscribers.filter(sub => sub.callback === callback) : subscribers; remove.forEach(sub => { const i = subscribers.indexOf(sub); subscribers.splice(i, 1); }); checkActiveSubscribers(); }; this.stop = () => { // Stop (remove) all subscriptions subscribers.splice(0); checkActiveSubscribers(); }; /** * For publishing side: adds a value that will trigger callbacks to all subscribers * @param val * @returns returns whether there are subscribers left */ const publish = (val) => { subscribers.forEach(sub => { try { sub.callback(val); } catch (err) { console.error(`Error running subscriber callback: ${err.message}`); } }); if (subscribers.length === 0) { checkActiveSubscribers(); } return subscribers.length > 0; }; /** * For publishing side: let subscribers know their subscription is activated. Should be called only once */ const start = (allSubscriptionsStoppedCallback) => { activationState = true; noMoreSubscribersCallback = allSubscriptionsStoppedCallback; subscribers.forEach(sub => { var _a; (_a = sub.activationCallback) === null || _a === void 0 ? void 0 : _a.call(sub, true); }); }; /** * For publishing side: let subscribers know their subscription has been canceled. Should be called only once */ const cancel = (reason) => { activationState = reason; subscribers.forEach(sub => { var _a; (_a = sub.activationCallback) === null || _a === void 0 ? void 0 : _a.call(sub, false, reason || new Error('unknown reason')); }); subscribers.splice(0); // Clear all }; const publisher = new EventPublisher(publish, start, cancel); eventPublisherCallback(publisher); } } exports.EventStream = EventStream; //# sourceMappingURL=subscription.js.map