doddle
Version:
Tiny yet feature-packed (async) iteration toolkit.
189 lines • 6.28 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Doddle = exports.ownerInstance = void 0;
exports.doddle = doddle;
exports.lazyOperator = lazyOperator;
exports.pull = pull;
const error_js_1 = require("../errors/error.js");
const utils_js_1 = require("../utils.js");
exports.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
*/
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[exports.ownerInstance] = this;
this[name] = bound;
}
(0, error_js_1.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) {
(0, error_js_1.chk)(this.catch).handler(handler);
return doddle(() => {
try {
const pulled = this.pull();
if ((0, utils_js_1.isThenable)(pulled)) {
return pulled.then(undefined, handler);
}
return pulled;
}
catch (e) {
return handler(e);
}
});
}
do(action) {
(0, error_js_1.chk)(this.do).action(action);
return this.map((x) => {
const result = action(x);
return doddle(() => {
return result;
}).map(() => x);
});
}
map(projection) {
const _projection = (0, error_js_1.chk)(this.map).projection(projection);
return doddle(() => {
const pulled = this.pull();
if ((0, utils_js_1.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 error_js_1.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 = (0, utils_js_1.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 ((0, utils_js_1.isThenable)(resource)) {
info.syncness = 2 /* Syncness.Async */;
resource = resource.then(value => {
if ((0, utils_js_1.isDoddle)(value)) {
value = value.pull();
}
info.stage = 2 /* Stage.Done */;
this._cacheName = (0, utils_js_1.getClassName)(value);
return value;
});
}
else {
info.syncness = 1 /* Syncness.Sync */;
info.stage = 2 /* Stage.Done */;
this._cacheName = (0, utils_js_1.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(utils_js_1.isThenable)) {
return Promise.all(values);
}
return values;
});
}
}
exports.Doddle = Doddle;
function doddle(initializer) {
if (!(0, utils_js_1.isFunction)(initializer)) {
throw new Error(`Initializer must be a function, but got ${(0, utils_js_1.getValueDesc)(initializer)}`);
}
if (exports.ownerInstance in initializer) {
return initializer[exports.ownerInstance];
}
return new Doddle(initializer);
}
/**
* Doddle utility functions.
*
* @category Create
*/
(function (doddle) {
doddle.is = utils_js_1.isDoddle;
})(doddle || (exports.doddle = doddle = {}));
/** @internal */
function lazyOperator(operand, func) {
const lz = doddle(() => func.call(operand, operand));
Object.assign(lz, {
operator: func.name,
operand
});
return lz;
}
function pull(input) {
return doddle(() => input).pull();
}
//# sourceMappingURL=index.js.map