type ResolveReject = [(value: void | PromiseLike) => void, (reason?: any) => void] /** * Represents a class that can be used to create a singleton instance of a class that is created asynchronously. */ export default class AsyncSingleton { private readonly factory: (r: R) => Promise private readonly checker: (value: T) => Promise private queue: ResolveReject[] = [] private instantiating: boolean = false private value?: T /** * Creates a new instance of the AsyncSingleton class. * @param factory The factory function that creates the instance. * @param checker The function that checks if the instance is valid. */ constructor( factory: (r: R) => Promise, checker: (value: T) => Promise = async () => true ) { this.factory = factory this.checker = checker } /** * Gets the instance of the class. * @param r The configuration object for creating the instance. */ public async instance(r: R): Promise { if (this.value && (await this.checker(this.value))) return this.value if (this.instantiating) { return await this.awaitOtherInit() } return await this.initHere(r) } /** * Gets the current value, may be undefined or invalid. */ public getValue(): T | undefined { return this.value } /** * Clears the current value and rejects all pending promises. */ public clear() { this.value = undefined for (const [, rej] of this.queue) { rej() } this.queue = [] } private async initHere(r: R): Promise { try { this.instantiating = true this.value = await this.factory(r) for (const [res] of this.queue) { res() } this.queue = [] return this.value } finally { for (const [, rej] of this.queue) { rej() } this.instantiating = false } } private async awaitOtherInit(): Promise { await new Promise((res, rej) => { this.queue.push([res, rej]) }) return this.value! } }