stdlazy
Version:
Flexible and debuggable lazy primitive.
236 lines • 8.59 kB
JavaScript
"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