UNPKG

doddle

Version:

Tiny yet feature-packed (async) iteration toolkit.

182 lines 5.86 kB
import { chk, DoddleError, loadCheckers } from "../errors/error.js"; import { getClassName, getValueDesc, isDoddle, isFunction, isThenable } from "../utils.js"; export const ownerInstance = Symbol("ownerInstance"); /** * A TypeScript-first doddle evaluation primitive. An object that will only evaluate its initializer * function when the {@link pull} method is called. * * The initializer can return another {@link Doddle}, which will be chained like a promise. * * @category Use */ export class Doddle { /** @ignore */ constructor(initializer) { this._info = { syncness: 0 /* Syncness.Untouched */, stage: 0 /* Stage.Untouched */ }; this._init = initializer; for (const name of ["map", "do", "zip", "catch", "pull"]) { const bound = this[name].bind(this); bound[ownerInstance] = this; this[name] = bound; } loadCheckers(this); } /** @internal */ get [Symbol.toStringTag]() { return "Doddle"; } /** Returns metadata about the current state of the Doddle. */ get info() { const { stage, syncness } = this._info; const syncnessWord = ["untouched", "sync", "async"][syncness]; const syncnessPart = syncness === 0 /* Syncness.Untouched */ ? [] : [syncnessWord]; const stageWord = ["untouched", "executing", "done", "threw"][stage]; const stagePart = stage === 2 /* Stage.Done */ ? this._cacheName : `<${stageWord}>`; return { isReady: stage >= 2 /* Stage.Done */, desc: ["doddle", ...syncnessPart, stagePart].join(" "), stage: stageWord, syncness: syncnessWord }; } catch(handler) { chk(this.catch).handler(handler); return doddle(() => { try { const pulled = this.pull(); if (isThenable(pulled)) { return pulled.then(undefined, handler); } return pulled; } catch (e) { return handler(e); } }); } do(action) { chk(this.do).action(action); return this.map((x) => { const result = action(x); return doddle(() => { return result; }).map(() => x); }); } map(projection) { const _projection = chk(this.map).projection(projection); return doddle(() => { const pulled = this.pull(); if (isThenable(pulled)) { return pulled.then(_projection); } return _projection(pulled); }); } /** * Returns a memoized function, which acts like this Doddle while hiding its type. * * @returns A memoized function that pulls `this` and returns its result. */ memoize() { return this.pull; } /** * Evaluates this {@link Doddle} instance, flattening any nested {@link Doddle} or {@link Promise} * types and yielding its value. * * @returns The yielded value. * @throws The error thrown during initialization, if any. */ pull() { const info = this._info; if (info.stage === 1 /* Stage.Executing */) { if (info.syncness === 2 /* Syncness.Async */) { return this._cached; } else { throw new DoddleError(`Tried to call 'Doddle.pull' recursively in a sync context, which would not terminate.`); } } if (info.stage === 2 /* Stage.Done */) { return this._cached; } info.stage = 1 /* Stage.Executing */; let resource; try { const result = this._init(); resource = isDoddle(result) ? result.pull() : result; } finally { if (!resource) { info.stage = 3 /* Stage.Threw */; } } // No need to keep holding a reference to the constructor. this._init = null; if (isThenable(resource)) { info.syncness = 2 /* Syncness.Async */; resource = resource.then(value => { if (isDoddle(value)) { value = value.pull(); } info.stage = 2 /* Stage.Done */; this._cacheName = getClassName(value); return value; }); } else { info.syncness = 1 /* Syncness.Sync */; info.stage = 2 /* Stage.Done */; this._cacheName = getClassName(resource); } this._cached = resource; return resource; } /** Returns a short description of the Doddle value and its state. */ toString() { return this.info.desc; } zip(...others) { return doddle(() => { const values = [this, ...others].map(x => x.pull()); if (values.some(isThenable)) { return Promise.all(values); } return values; }); } } export function doddle(initializer) { if (!isFunction(initializer)) { throw new Error(`Initializer must be a function, but got ${getValueDesc(initializer)}`); } if (ownerInstance in initializer) { return initializer[ownerInstance]; } return new Doddle(initializer); } /** * Doddle utility functions. * * @category Create */ (function (doddle) { doddle.is = isDoddle; })(doddle || (doddle = {})); /** @internal */ export function lazyOperator(operand, func) { const lz = doddle(() => func.call(operand, operand)); Object.assign(lz, { operator: func.name, operand }); return lz; } export function pull(input) { return doddle(() => input).pull(); } //# sourceMappingURL=index.js.map