UNPKG

mahler

Version:

A automated task composer and HTN based planner for building autonomous system agents

279 lines • 8.06 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Observable = exports.Subject = exports.UnhandledObservableError = void 0; exports.interval = interval; const util_1 = require("util"); class UnhandledObservableError extends Error { constructor(cause) { super('An error in an observable was uncaught by observer', { cause }); } } exports.UnhandledObservableError = UnhandledObservableError; /** * A Subject is a special type of observable tha allows values to be * multicasted to many observers. * * We use the name Subject as is the terminology used by rxjs * https://rxjs.dev/guide/subject */ class Subject { subscribers = []; // Removes all subscribers cleanup() { while (this.subscribers.length > 0) { this.subscribers.pop(); } } next(t) { this.subscribers.forEach((s) => { s.next(t); }); } error(e) { this.subscribers.forEach((s) => { s.error(e); }); // We assume the observable operation terminates // after an error this.cleanup(); } complete() { this.subscribers.forEach((s) => { s.complete(); }); // We assume the observable operation terminates // after complete is called this.cleanup(); } subscribe(next) { let s; if (typeof next === 'function') { s = { next, error: () => void 0, complete: () => void 0, }; } else { s = next; } this.subscribers.push(s); const self = this; return { unsubscribe() { const index = self.subscribers.indexOf(s); if (index !== -1) { self.subscribers.splice(index, 1); } }, }; } } exports.Subject = Subject; function isPromiseLike(x) { if (x instanceof Promise) { return true; } return (x != null && (typeof x === 'function' || typeof x === 'object') && typeof x.then === 'function'); } function isSubscribable(x) { return x != null && typeof x.subscribe === 'function'; } function isSyncIterable(x) { return x != null && typeof x === 'object' && Symbol.iterator in x; } function isIterable(x) { return (x != null && typeof x === 'object' && (Symbol.iterator in x || Symbol.asyncIterator in x)); } async function processPromise(p, subscriber) { const t = await p; if (subscriber.closed) { return; } subscriber.next(t); subscriber.complete(); } async function processIterable(input, subscriber) { const items = input[Symbol.asyncIterator](); let n = await items.next(); while (!n.done) { if (subscriber.closed) { return; } subscriber.next(n.value); n = await items.next(); } subscriber.complete(); } /** * Multiplexes an iterable so that multiple iterators can consume it * * This returns a function that can be called to create a new iterator. Each * iterator will receive the same values in the same order. */ function multiplexIterable(input) { const items = isSyncIterable(input) ? input[Symbol.iterator]() : input[Symbol.asyncIterator](); const consumers = []; let running = false; async function readFromInput() { if (running || consumers.length === 0) { return; } running = true; try { // Get the next result and pass it to all consumers waiting for // it const result = await Promise.resolve(items.next()); let c = consumers.shift(); // Pass the resulting value to all consumers waiting for it while (c != null) { c.resolve(result); c = consumers.shift(); } // Once all consumers have been served we will only get the next result // once next() has been called in one of the output iterators } catch (e) { consumers.forEach((c) => { c.reject(e); }); consumers.length = 0; } finally { running = false; } } return function () { return { [Symbol.asyncIterator]() { return { next() { const promise = new Promise((resolve, reject) => { consumers.push({ resolve, reject }); }); void readFromInput(); return promise; }, }; }, }; }; } function from(input) { let items; if (isIterable(input)) { items = multiplexIterable(input); } const self = { subscribe(next) { let s; if (typeof next === 'function') { s = { next, // This will result in an unhandledRejection as the // promise is not awaited below. While this is not ideal, // those errors can be detected through the node `unhandledRejection` // event https://nodejs.org/api/process.html#event-unhandledrejection // when in doubt, users should pass an error handler // QUESTION: should we panic here? error: (e) => { throw new UnhandledObservableError(e); }, complete: () => void 0, closed: false, }; } else { s = { ...next, closed: false }; } if (isSubscribable(input)) { return input.subscribe(s); } if (isPromiseLike(input)) { processPromise(input, s).catch(s.error); } else { processIterable(items(), s).catch(s.error); } return { unsubscribe: () => { s.closed = true; s.next = () => void 0; }, }; }, map(f) { return from(map(self, f)); }, filter(f) { return from(filter(self, f)); }, }; return self; } function map(o, f) { // QUESTION: should we memoize f so it's called at most once with // each value? Currently, it will be N*M times where N is the number of subscriptors // and M is the number of calls per subscriptor return { subscribe(subscriber) { return o.subscribe({ ...subscriber, next: (t) => { subscriber.next(f(t)); }, }); }, }; } function filter(o, f) { return { subscribe(subscriber) { return o.subscribe({ ...subscriber, next: (t) => { if (f(t)) { subscriber.next(t); } }, }); }, }; } function of(...values) { return from(values); } function is(x) { return isSubscribable(x) && typeof x.map === 'function'; } /** * Utility function to return a value on an interval. * This is a useful observable to build new observables from */ function interval(periodMs) { // We use promisify instead of `timers/promises` because it works // works for testing with sinon faketimers const sleep = (0, util_1.promisify)(setTimeout); return exports.Observable.from((async function* () { let i = 0; while (true) { await sleep(periodMs); yield i++; } })()); } exports.Observable = { of, from, is, map, filter, interval, }; //# sourceMappingURL=observable.js.map