doddle
Version:
Tiny yet feature-packed (async) iteration toolkit.
182 lines • 5.86 kB
JavaScript
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