nope-js-node
Version:
NoPE Runtime for Nodejs. For Browser-Support please use nope-browser
345 lines (344 loc) • 11.9 kB
JavaScript
;
/**
* @author Martin Karkowski
* @email m.karkowski@zema.de
* @create date 2020-11-23 08:06:30
* @modify date 2021-10-19 17:55:35
* @desc [description]
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.NopeEventEmitter = void 0;
const operators_1 = require("rxjs/operators");
const getSubject_1 = require("../helpers/getSubject");
const idMethods_1 = require("../helpers/idMethods");
const runtimeMethods_1 = require("../helpers/runtimeMethods");
const getLogger_1 = require("../logger/getLogger");
const logger = (0, getLogger_1.getNopeLogger)("obervable");
/**
* RsJX based Observable.
*
* Contains additional Functionalities like:
* - property with the current value
* - function to publish values. (wrapper for next)
* - enables performing a subscription with synced call or a immediate call.
*/
class NopeEventEmitter {
get getter() {
return this._getter;
}
set getter(_getter) {
this._getter = _getter;
}
emit(value, options = {}) {
return this._emit(value, options);
}
/**
* Function to set the content of the Observable
* @param value
* @param sender
* @param timeStamp
* @param data
*/
_emit(value, options = {}) {
let _value = value;
// Change the Value.
if (this.setter !== null) {
const adapted = this.setter(value, options);
if (!adapted.valid) {
return false;
}
_value = adapted.data;
}
_value = this.getter !== null ? this.getter(_value) : _value;
// Publish the data.
if (options.forced || this.disablePublishing === false) {
options = this._updateSenderAndTimestamp(options);
// Define the value.
this._emitter.next({ value: _value, ...options });
return this.hasSubscriptions;
}
return false;
}
/**
* Helper to update the Timestamp and sender
*
* @author M.Karkowski
* @protected
* @param {IEventAdditionalData} options
* @return {*} {ISetContentOptions}
* @memberof NopeObservable
*/
_updateSenderAndTimestamp(options) {
// Define a Sender if required
if (options.sender === undefined) {
options.sender = this.id;
}
// Generate a Timestamp if required.
if (this.options.generateTimeStamp === true) {
options.timestamp =
options.timestamp === undefined ? Date.now() : options.timestamp;
}
// Return the adapted element.
return options;
}
/**
* Function, used to dispose the observable.
* Every item will be unsubscribed.
*/
dispose() {
for (const _unsubscribe of this._subscriptions) {
_unsubscribe();
}
this._subscriptions.clear();
this._emitter.closed = true;
}
/**
* A Function to subscribe to updates of the Observable.
* @param observer The Observer. Could be a Function or a Partial Observer.
* @param mode The Mode of the Subscription
* @param options Additional Options.
*/
subscribe(observer, options = {
type: "sync",
mode: ["direct", "sub", "super"],
}) {
options.skipCurrent =
!!this._options.showCurrent && !this._options.playHistory;
return this._subscribe(observer, options);
}
_subscribe(observer, options = {
type: "sync",
mode: ["direct", "sub", "super"],
}) {
const _this = this;
let active = true;
let _observer;
let _first = true;
if (typeof observer === "object") {
_observer = {
next: (data) => {
// Make shure we are skipping the current Item, if desired
if (_first && options.skipCurrent) {
_first = false;
return;
}
_first = false;
if (active && data !== undefined && observer.next) {
const { value, ...rest } = data;
switch (options.type) {
case "immediate":
(0, runtimeMethods_1.callImmediate)(observer.next, value, rest);
break;
default:
observer.next(value, rest);
break;
case "sync":
observer.next(value, rest);
break;
}
}
},
complete: () => {
if (observer.complete) {
observer.complete();
}
},
error: (error) => {
if (observer.error) {
observer.error(error);
}
},
};
}
else if (typeof observer === "function") {
_observer = {
next: (data) => {
// Make shure we are skipping the current Item, if desired
if (_first && options.skipCurrent) {
_first = false;
return;
}
_first = false;
if (active && data !== undefined) {
const { value, ...rest } = data;
switch (options.type) {
case "immediate":
(0, runtimeMethods_1.callImmediate)(observer, value, rest);
break;
default:
observer(value, rest);
break;
case "sync":
observer(value, rest);
break;
}
}
},
complete: () => {
// Nothing to do here
},
error: (error) => {
logger.error("");
logger.error(error);
},
};
}
// Create a Subscription.
const subscription = this._emitter.subscribe(_observer);
const ret = Object.assign(subscription, {
options,
pause: () => {
active = false;
},
unpause: () => {
active = true;
},
});
return ret;
}
/**
* Create an enhanced Subscription of the Observable. Use the Pipes, to
* Define what should be subscribed.
* @param next The Next Function, used to transmit changes
* @param options The Options, used to determine the Enhancements.
*/
enhancedSubscription(next, options = {}) {
let observable = this;
if (options.pipe) {
observable = options.pipe(options.scope, this._emitter.pipe((0, operators_1.map)((value) => {
return value.value;
})));
}
const subscription = observable.subscribe({
next,
});
return subscription;
}
/**
* Creates a Subscription for the value of the Observable. After one Update the Value will be deleted
* @param func Function which is called when new Datas are pushed
* @param mode Mode of the Subscription
* @param options Additional Options
*/
once(func, options) {
let ret = null;
ret = this.subscribe({
next: (...args) => {
ret.unsubscribe();
func(...args);
},
}, options);
return ret;
}
/**
* Async Function to Wait for an Update
* @param mode Mode of the Subscription
* @param options Additional Options for the Wait Function.
*/
waitFor(testCallback = (value) => {
return value == true;
}, options = { testCurrent: true }) {
const _this = this;
let resolved = false;
let subscription = null;
let timeout = null;
return new Promise((resolve, reject) => {
const finish = (error, test, data) => {
// Reject the error.
if (error) {
reject(error);
}
if (timeout) {
clearTimeout(timeout);
}
// Unsubscribe the Subscription.
if (test && subscription) {
subscription.unsubscribe();
subscription = null;
}
if (test && !resolved) {
// Mark the Task as Resolved.
resolved = true;
resolve(data);
}
};
let first = true;
const checkData = (data, opts) => {
if ((first && options.testCurrent) || !first) {
// Create a promise of the data
const prom = Promise.resolve(testCallback(data, opts));
// Now we link the element
prom.catch((e) => {
finish(e, false, data);
});
prom.then((r) => {
finish(false, r, data);
});
}
first = false;
};
try {
subscription = _this.subscribe((data, opts) => {
checkData(data, opts);
});
if ((options === null || options === void 0 ? void 0 : options.timeout) > 0) {
timeout = setTimeout(() => {
finish(Error("Timeout.!"), false, null);
}, options.timeout);
}
}
catch (e) {
reject(e);
}
});
}
/**
* Async Function to Wait for an Update
* @param mode Mode of the Subscription
* @param options Additional Options for the Wait Function.
*/
waitForUpdate(options) {
const _this = this;
return new Promise((resolve, reject) => {
try {
_this.once((content) => {
resolve(content);
}, options);
}
catch (e) {
reject(e);
}
});
}
get hasSubscriptions() {
return this._emitter.observed;
}
get observerLength() {
return this._emitter.observers.length;
}
constructor(_options = {}) {
this._options = _options;
this.id = (0, idMethods_1.generateId)();
this.options = {
generateTimeStamp: true,
};
/**
* Function to specify a Setter
*/
this.setter = null;
/**
* Function to specify a Getter
*/
this._getter = null;
/**
* A Set containing the Subscriptions
*/
this._subscriptions = new Set();
/**
* Flag to Disable Publishing
*/
this.disablePublishing = false;
this._emitter = (0, getSubject_1.getSubject)(_options);
}
}
exports.NopeEventEmitter = NopeEventEmitter;