enum ResolvableState { WAITING, PENDING, ERROR, DONE } class FetchOnce { protected data: T|undefined; protected error: unknown|undefined; protected state: ResolvableState = ResolvableState.WAITING; protected pendings: [(res: Promise|T) => void, (reason: unknown) => void][] = []; constructor(protected fetchMethod?: () => Promise) { } public resolve(): Promise { return new Promise((resolve, reject) => { switch (this.state) { case ResolvableState.WAITING: this.state = ResolvableState.PENDING; this.pendings.push([resolve, reject]); if (this.fetchMethod) this.parsePromise(this.fetchMethod()); break; case ResolvableState.PENDING: this.pendings.push([resolve, reject]); break; case ResolvableState.DONE: resolve(this.data); break; case ResolvableState.ERROR: reject(this.error); break; } }); } protected isFinished(): boolean { return this.state === ResolvableState.DONE || this.state === ResolvableState.ERROR; } protected parsePromise(promise: Promise): void { promise.then((data) => { this.data = data; this.state = ResolvableState.DONE; this.pendings.forEach(pending => pending[0](data)); }).catch(err => { this.error = err; this.state = ResolvableState.ERROR; this.pendings.forEach(pending => pending[1](err)); }); } } export class Resolvable extends FetchOnce { constructor(fetchMethod: () => Promise) { super(fetchMethod); } } export class WaitForSync extends FetchOnce { protected state: ResolvableState = ResolvableState.PENDING; constructor() { super(undefined); } public setData(data: T): void { if (!this.isFinished()) { this.parsePromise((async () => data)()); } } public setError(error: unknown): void { if (!this.isFinished()) { this.parsePromise((async () => { throw error; })()); } } }