import { Future } from "./future.js"; import { Option } from "./option.js"; import { ResolveOnce } from "./resolve-once.js"; /** * Configuration options for WaitingForValue. */ export interface WaitingForValueProps { /** * Optional preset value to initialize the WaitingForValue with. * If provided and Some, the value is immediately available. */ readonly presetValue?: Option; } /** * A utility for managing a value that may not be immediately available. * Allows multiple callers to await the same value efficiently, with all * waiters resolved when the value becomes available. * * @template T - The type of value being waited for * * @example * ```ts * const waiting = new WaitingForValue(); * * // Multiple callers can await the value * const promise1 = waiting.waitValue(); * const promise2 = waiting.waitValue(); * * // Set the value - all waiters are resolved * waiting.setValue(Option.Some("hello")); * * // Both promises resolve to "hello" * ``` */ export class WaitingForValue { #value!: Option; #waitFuture?: Future; #waitValue!: () => Promise; /** * Creates a new WaitingForValue instance. * * @param opts - Configuration options */ constructor(opts: WaitingForValueProps = {}) { this.init(opts.presetValue); } /** * Initializes or reinitializes the WaitingForValue with an optional preset value. * If a preset value is provided and is Some, the value is immediately available. * Otherwise, sets up the waiting mechanism for future value resolution. * * @param presetValue - Optional initial value */ init(presetValue: Option = Option.None()): void { this.#value = presetValue; if (presetValue.IsSome() && this.#waitFuture) { this.#waitFuture.resolve(presetValue.unwrap()); } if (this.#value.IsSome()) { // console.log("setValue: 3-Some:", this.#value, value) this.#waitFuture = undefined; const resolveOnce = new ResolveOnce(); this.#waitValue = (): Promise => resolveOnce.once(() => Promise.resolve(presetValue.unwrap())); return; } const myFuture = (this.#waitFuture = new Future()); const resolveOnce = new ResolveOnce(); this.#waitValue = (): Promise => resolveOnce.once(async () => myFuture.asPromise()); } /** * Sets the value and resolves all pending waiters. * If called when no value exists, resolves the waiting Future. * If called when a value already exists, updates the value and resets the waiting mechanism. * Does nothing if the provided value is None. * * @param value - The value to set (wrapped in Option) */ setValue(value: Option): void { // console.log("setValue", value, this.#value) switch (true) { case this.#value.IsNone(): // console.log("setValue: 2") this.#value = value; if (value.IsSome()) { this.#waitFuture?.resolve(value.unwrap()); } break; case this.#value.IsSome(): this.init(value); break; } } setError(err: Error): void { if (this.#value.IsSome()) { // ignore error if value is already set // eslint-disable-next-line no-console console.warn("WaitingForValue.setError called but value is already set, ignoring error", err); return; } this.#waitFuture?.reject(err); this.init(); } /** * Returns a promise that resolves when the value becomes available. * If the value is already set, returns a promise that resolves immediately. * Multiple calls to this method are safe and efficient - all callers share * the same resolution logic thanks to ResolveOnce. * * @returns A promise that resolves to the value */ waitValue(): Promise { return this.#waitValue(); } value(): Option { return this.#value; } }