diff options
Diffstat (limited to 'packages/backend/src/misc/loader.ts')
| -rw-r--r-- | packages/backend/src/misc/loader.ts | 52 |
1 files changed, 52 insertions, 0 deletions
diff --git a/packages/backend/src/misc/loader.ts b/packages/backend/src/misc/loader.ts new file mode 100644 index 0000000000..25f7b54d31 --- /dev/null +++ b/packages/backend/src/misc/loader.ts @@ -0,0 +1,52 @@ +export type FetchFunction<K, V> = (key: K) => Promise<V>; + +type ResolveReject<V> = Parameters<ConstructorParameters<typeof Promise<V>>[0]>; + +type ResolverPair<V> = { + resolve: ResolveReject<V>[0]; + reject: ResolveReject<V>[1]; +}; + +export class DebounceLoader<K, V> { + private resolverMap = new Map<K, ResolverPair<V>>(); + private promiseMap = new Map<K, Promise<V>>(); + private resolvedPromise = Promise.resolve(); + constructor(private loadFn: FetchFunction<K, V>) {} + + public load(key: K): Promise<V> { + const promise = this.promiseMap.get(key); + if (typeof promise !== 'undefined') { + return promise; + } + + const isFirst = this.promiseMap.size === 0; + const newPromise = new Promise<V>((resolve, reject) => { + this.resolverMap.set(key, { resolve, reject }); + }); + this.promiseMap.set(key, newPromise); + + if (isFirst) { + this.enqueueDebouncedLoadJob(); + } + + return newPromise; + } + + private runDebouncedLoad(): void { + const resolvers = [...this.resolverMap]; + this.resolverMap.clear(); + this.promiseMap.clear(); + + for (const [key, { resolve, reject }] of resolvers) { + this.loadFn(key).then(resolve, reject); + } + } + + private enqueueDebouncedLoadJob(): void { + this.resolvedPromise.then(() => { + process.nextTick(() => { + this.runDebouncedLoad(); + }); + }); + } +} |