UNPKG

stdlazy

Version:

Flexible and debuggable lazy primitive.

236 lines 8.59 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Lazy = exports.ownerInstance = exports.methodName = void 0; /* eslint-disable @typescript-eslint/await-thenable */ const config = __importStar(require("./config")); const ctor_1 = require("./ctor"); const errors_1 = require("./errors"); const types_1 = require("./types"); const utils_1 = require("./utils"); exports.methodName = Symbol("methodName"); exports.ownerInstance = Symbol("ownerInstance"); /** * A TypeScript-first lazy evaluation primitive. A {@link Pullable} that will only evaluate its * initializer function when the {@link pull} method is called. * * The initializer can return another {@link Lazy}, which will be chained like a promise. */ class Lazy { /** The cached value or error, stored from a previous execution of the initializer. */ _cached; _desc; _info; get info() { return this._info; } /** * The initializer function that will be called to construct the value. It will be cleared after * the value is constructed, unless `LAZY_NOCLEAR` is set. */ _init; /** Has the initializer finished executing? */ get isReady() { return this._info.stage === "done"; } *[Symbol.iterator]() { const inner = this.pull(); if ((0, utils_1.isIterable)(inner)) { yield* inner; } yield inner; } async *[Symbol.asyncIterator]() { // eslint-disable @typescript-eslint/await-thenable const inner = await this.pull(); if ((0, utils_1.isAsyncIterable)(inner)) { yield* inner; } yield inner; } constructor(initializer) { this._info = { stage: "untouched", syncness: "untouched", name: (0, types_1.getInitializerName)(initializer) }; this._desc = this._makeDescription(); this._init = initializer; const anyMe = this; for (const key of ["pull", "map", "do", "zip", "assemble"]) { anyMe[key] = anyMe[key].bind(this); anyMe[key][exports.ownerInstance] = this; anyMe[key][exports.methodName] = key; } } static create(f) { return new Lazy(f); } _makeDescription(resolved) { const asyncPart = this._info.syncness === "untouched" ? [] : [this._info.syncness]; const stagePart = this._info.stage === "done" ? (0, types_1.getClassName)(resolved) : `<${this._info.stage}>`; const name = this._info.name ? `lazy(${this._info.name})` : "lazy"; return [name, ...asyncPart, stagePart].join(" "); } /** Returns a short description of the Lazy value and its state. */ toString() { return this._desc; } /** * Evaluates this {@link Lazy} instance, flattening any nested {@link Lazy} or {@link Promise} * types. * * @returns The value produced by the initializer, after flattening any nested {@link Lazy} or * {@link Promise} instances. * @throws The error thrown during initialization, if any. */ pull() { const info = this._info; if (info.stage === "threw") { // Correct way to return the error throw this._cached; } if (info.stage === "executing") { if (info.syncness === "async") { return this._cached; } else { throw (0, errors_1.cannotRecurseSync)(); } } if (info.stage === "done") { return this._cached; } info.stage = "executing"; this._desc = this._makeDescription(); let resource; try { const result = this._init(); resource = (0, types_1.isPullable)(result) ? result.pull() : result; } catch (e) { this._cached = e; info.stage = "threw"; this._desc = this._makeDescription(); throw e; } // No need to keep holding a reference to the constructor. if (!config.LAZY_NOCLEAR) { this._init = null; } if ((0, types_1.isThenable)(resource)) { info.syncness = "async"; resource = resource.then(value => { if ((0, types_1.isPullable)(value)) { value = value.pull(); } info.stage = "done"; this._desc = this._makeDescription(value); return value; }); } else { info.syncness = "sync"; info.stage = "done"; } this._cached = resource; this._desc = this._makeDescription(); return resource; } get [Symbol.toStringTag]() { return this.toString(); } map(projection) { return (0, ctor_1.lazy)(() => { const pulled = this.pull(); if ((0, types_1.isThenable)(pulled)) { return pulled.then(projection); } return projection(pulled); }); } do(callback) { return this.map(x => { const result = callback(x); return (0, ctor_1.lazy)(() => { return result; }).map(() => x); }); } zip(...others) { // eslint-disable-next-line @typescript-eslint/promise-function-async return (0, ctor_1.lazy)(() => { const values = [this, ...others].map(x => x.pull()); if (values.some(types_1.isThenable)) { return Promise.all(values); } return values; }); } /** * Takes an key-value object with {@link Lazy} values and returns a new {@link Lazy} that, when * pulled, will pull all of them and return an object with the same keys, but with the values * replaced by the pulled results. If any of the values are async, the new {@link Lazy} will also * be async. * * The value of **this** {@link Lazy} will be available under the key `"this"`. * * @example * const self = lazy(() => 1).assemble({ * a: lazy(() => 2), * b: lazy(() => 3) * }) * expect(self.pull()).toEqual({ this: 1, a: 2, b: 3 }) * * const asyncSelf = lazy(async () => 1).assemble({ * a: lazy(() => 2), * b: lazy(() => 3) * }) * await expect(asyncSelf.pull()).resolves.toEqual({ this: 1, a: 2, b: 3 }) * * @param assembly An object with {@link Lazy} values. * @returns A new {@link Lazy} primitive that will return an object with the same keys as the * input object, plus the key `"this"`, with the pulled results. * @summary Converts an object of {@link Lazy} values into a {@link Lazy} value producing an object. */ assemble(assembly) { return (0, ctor_1.lazy)(() => { const keys = ["this", ...Object.keys(assembly)]; const values = [this, ...Object.values(assembly)].map((x) => x.pull()); if (values.some(types_1.isThenable)) { return Promise.all(values).then(values => keys.reduce((acc, key, i) => { acc[key] = values[i]; return acc; }, {})); } return values.reduce((acc, value, i) => { acc[keys[i]] = value; return acc; }, {}); }); } } exports.Lazy = Lazy; //# sourceMappingURL=lazy.js.map