diff --git a/src/lib/data_structures/lazy.ts b/src/lib/data_structures/lazy.ts index 1589cba..d6d6035 100644 --- a/src/lib/data_structures/lazy.ts +++ b/src/lib/data_structures/lazy.ts @@ -5,8 +5,9 @@ export enum LazyStatus { } export class Lazy { - #value?: T; + #value: T | null = null; #resolver: () => Promise; + #pendingPromise: Promise | null = null; status: LazyStatus; @@ -15,18 +16,40 @@ export class Lazy { this.status = LazyStatus.Pending; } - async value(): Promise { - if (!this.#value) { - try { - this.#value = await this.#resolver(); - } catch (error) { - this.status = LazyStatus.Error; - console.error(error); - return null; - } + /** + * Resolves the lazy object and returns the value. + * + * @returns The resolved value. + * + * @remarks Lazy object resolution is performed as an atomic operation. If a resolution has + * already been requested when this function is invoked, the pending promise from the earlier + * invocation is returned. Thus, all calls to this function before it is resolved will depend on + * a single resolution. + */ + value(): Promise { + if (this.status === LazyStatus.Resolved) { + return Promise.resolve(this.#value); } - this.status = LazyStatus.Resolved; - return this.#value; + if (this.#pendingPromise) { + return this.#pendingPromise; + } + + this.#pendingPromise = this.#resolve(); + return this.#pendingPromise; + } + + async #resolve(): Promise { + try { + this.#value = await this.#resolver(); + this.status = LazyStatus.Resolved; + return this.#value; + } catch (error) { + this.status = LazyStatus.Error; + console.error(error); + return null; + } finally { + this.#pendingPromise = null; + } } } \ No newline at end of file