import { action, IReactionDisposer, toJS } from 'mobx'; import Cache, { refreshCacheOptions } from '../cache'; const KEY = Symbol('KEY'); export default class PromiseMerger<K, ARGS, V> { timeout: number; cache: Cache<string, K>; dataMap: Map<string, V>; promiseMap: Map<string | symbol, Map<string, { resolves: Function[]; rejects: Function[] }>>; waitID; callback: (codes: string[], args: ARGS, dataList: V[]) => Promise<{ [key: string]: K }>; reaction: IReactionDisposer; constructor(callback: (codes: string[], args: ARGS, dataList: V[]) => Promise<{ [key: string]: K }>, config, timeout = 200) { this.timeout = timeout; this.promiseMap = new Map<string | symbol, Map<string, { resolves: Function[]; rejects: Function[] }>>(); this.cache = new Cache<string, K>(toJS(config)); this.dataMap = new Map<string, V>(); this.callback = callback; this.reaction = refreshCacheOptions(this.cache); } dispose() { this.reaction(); } @action add(code: string, getBatchKey: ((defaultKey: symbol) => string | symbol) | undefined, args: ARGS, data?: V): Promise<K> { const { cache, promiseMap, dataMap } = this; const item = cache.get(code); if (item !== undefined) { return Promise.resolve(item); } if (data) { dataMap.set(code, data); } const batchKey = getBatchKey ? getBatchKey(KEY) : KEY; return new Promise<K>((resolve, reject) => { const promiseList = promiseMap.get(batchKey) || new Map(); promiseMap.set(batchKey, promiseList); let promise = promiseList.get(code); const resolveCallback = () => { resolve(cache.get(code)); }; if (promise) { promise.resolves.push(resolveCallback); promise.rejects.push(reject); } else { if (this.waitID) { clearTimeout(this.waitID); } promise = { resolves: [resolveCallback], rejects: [reject], }; promiseList.set(code, promise); this.waitID = setTimeout(() => { const codeList: string[] = Array.from(promiseList.keys()); if (process.env.LOGGER_LEVEL === 'info') { // eslint-disable-next-line no-console console.info(`batch request: ${codeList}`); } this.callback(codeList, args, codeList.reduce<V[]>((list, $code) => { const $value = dataMap.get($code); if ($value !== undefined) { list.push($value); } return list; }, [])).then(res => { codeList.forEach((key) => { const value = promiseList.get(key); promiseList.delete(key); const { resolves = [] } = value || {}; if (res) { const data = res[key]; this.cache.set(key, data); resolves.forEach(r => r(data)); } else { resolves.forEach(r => r()); } }); }) .catch(error => { codeList.forEach(key => { const value = promiseList.get(key); promiseList.delete(key); const { rejects = [] } = value || {}; rejects.forEach(r => r(error)); }); }); }, this.timeout); } }); } }