lib/util/lazyBuilder.ts (43 lines of code) (raw):

interface BuilderListener<T> { resolve: (result: T) => void; reject: (err: any) => void; } export const getLazyBuilder = <SourceType, Key extends keyof SourceType>( key: Key, builder: (source: SourceType) => Promise<Exclude<SourceType[Key], undefined>> ) => { const errKey = "_err_" + key; const listeners = new Map<SourceType, Array<BuilderListener<SourceType[Key]>>>(); return async (source: SourceType): Promise<Exclude<SourceType[Key], undefined>> => { let val = source[key]; if (val !== undefined) { return val as any; } const err = (source as any)[errKey]; if (err !== undefined) { throw err; } let listener = listeners.get(source); if (listener !== undefined) { return new Promise<any>((resolve, reject) => listener!.push({ resolve, reject })); } listener = []; listeners.set(source, listener); try { val = await builder(source); // eslint-disable-next-line require-atomic-updates source[key] = val; for (const { resolve } of listener) { resolve(val); } return val as any; } catch (e) { (source as any)[errKey] = e; for (const { reject } of listener) { reject(e); } throw e; } finally { listeners.delete(source); } }; };