Lazy

import { Lazy } from "jsr:@ende/async";

class

class Lazy<T>

A lazy value that is initialized at most once, with built-in deduplication of concurrent callers. Prevents the common race where two concurrent get() calls both trigger the initializer; only one initialization runs and all callers share the same promise.

If the initializer rejects, the error is propagated to all concurrent callers and the internal state is cleared — the next Lazy.prototype.get call will re-run the initializer. Compose with retry for automatic back-off on transient failures.

Example: Concurrent deduplication

import { Lazy } from "@ende/async/lazy";
import { assertEquals } from "@ende/assert";

let initCount = 0;
const config = new Lazy<number>(async () => {
  initCount++;
  return 42;
});

const [a, b] = await Promise.all([config.get(), config.get()]);
assertEquals(a, 42);
assertEquals(b, 42);
assertEquals(initCount, 1);

Example: Composing with retry

import { Lazy } from "@ende/async/lazy";
import { retry } from "@ende/async/retry";

const db = new Lazy(() =>
  retry(() => connectDb(), { minTimeout: 100, maxAttempts: 3 })
);
await db.get();

Constructors

constructor

new Lazy(init: () => T | Promise<T>)

Creates a new lazy value.

init: () => T | Promise<T>

Initializer function, called at most once (until reset).

Methods

get

get(options?: LazyGetOptions): Promise<T>

Returns the cached value, initializing it on first call. Concurrent callers share the same in-flight promise — the initializer is never invoked more than once at a time.

Always returns a promise, even when the initializer is synchronous.

options?: LazyGetOptions

Optional settings for this call.

Returns

The cached or newly initialized value.

Example: Usage

import { Lazy } from "@ende/async/lazy";

const config = new Lazy(async () => ({ loaded: true }));
const value = await config.get();

Example: Abort a slow initialization

import { Lazy } from "@ende/async/lazy";
import { assertRejects } from "@ende/assert";

const slow = new Lazy(() => new Promise<string>(() => {}));
const controller = new AbortController();
controller.abort(new Error("timed out"));
await assertRejects(
  () => slow.get({ signal: controller.signal }),
  Error,
  "timed out",
);

initialized

get initialized(): boolean

Whether the value has been successfully initialized.

Returns

true if the value has been initialized, false otherwise.

Example: Check initialization state

import { Lazy } from "@ende/async/lazy";
import { assertEquals } from "@ende/assert";

const lazy = new Lazy(() => 42);
assertEquals(lazy.initialized, false);
await lazy.get();
assertEquals(lazy.initialized, true);

peek

peek(): { ok: true; value: T } | { ok: false }

Returns the value if already resolved, or indicates that it is not yet available. The discriminated union avoids ambiguity when T itself can be undefined.

Returns

{ ok: true, value } if the value has been initialized, or { ok: false } if not yet initialized or still in-flight.

Example: Fast-path when already initialized

import { Lazy } from "@ende/async/lazy";
import { assertEquals } from "@ende/assert";

const config = new Lazy(async () => ({ port: 8080 }));
await config.get();

const result = config.peek();
assertEquals(result, { ok: true, value: { port: 8080 } });

Example: Not yet initialized

import { Lazy } from "@ende/async/lazy";
import { assertEquals } from "@ende/assert";

const lazy = new Lazy(() => 42);
assertEquals(lazy.peek(), { ok: false });

reset

reset(): void

Resets the lazy so the next get re-runs the initializer. Does not cancel an in-flight initialization; callers that already have the promise will still receive its result.

Example: Force reload

import { Lazy } from "@ende/async/lazy";

const config = new Lazy(async () => loadConfig());
await config.get();
config.reset();
const fresh = await config.get();

Source