我们已经实现了自动把同一时刻的请求合并,并统一渲染。
存在问题
老样子,先来看图回顾一下(假设一次 EventLoop 占用 5 个时间单位,请求回复也需要 5 个时间单位):
由图可见,我们遇到了一个新的问题:
同一时刻,最多只能合并一个 batch 请求,导致多次重复的同类型请求,无法合并成多个 batch 请求(如上图的 2 个 A、2 个 B,最后只合成了 1 个 batch、1 个 A 和 1 个 B,而非 2 个 batch)。
我们的期望是尽可能合并 batch 请求,如上图第 3、第 4 个请求可以合并成一个新的 batch 请求,并在 5 个时间单位的时候发送。
所以本文对原有的代码又做了一次优化。
具体实现
因为原先是用一个对象来保存多个请求的信息,键名容易重,这才导致无法实现。
所以我们的方案就是,把原有的对象形式转为数组形式,以此来存储信息,而数组的长度就代表了 batch
请求的次数。
注:之所以没法只用一个 batch
请求的原因是,batch
请求的参数无法支持同时处理多个同名请求。
衔接上一篇的 IRequestMap
对象,我们定义如下数组来代表 batch 队列:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
interface IRequestItem { name: string; data: IObject; success: (obj: IObject) => void; error: (err: Error) => void; };
interface IRequestMap { [url: string]: IRequestItem; };
let batchList: IRequestMap[] = [];
|
在我们编写 batch 队列处理的逻辑前,不妨先考虑一下,当一个新的请求,即 IRequestItem
对象进来时,我们该如何处理。
所以 addRequest
可调整为如下写法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
const addRequest: (requestItem: IRequestItem) => void = (requestItem) => { const { name } = requestItem;
const lastIndex = Math.max( -1, ...batchList.map((item: IRequestMap, index: number) => (item[name] ? index : -1)) );
if (lastIndex + 1 === batchList.length) { batchList.push({ name: requestItem }); } else if (lastIndex) { batchList[lastIndex + 1][name] = requestItem; } };
|
相应地,clearRequest
也可以简单调整成这样:
1 2 3 4 5 6
|
const clearRequest: () => void = () => { batchList = []; };
|
但是,clearRequest
的调用时机却发生了变化。
我们再来梳理一下,之前 clearRequest
是在 Promise.resolve().then()
的回调中,发完请求之后执行。
但是现在同一个 EventLoop 会包含多个 batch 队列,而每个 batch 队列都对应一个 Promise.resolve()
,也就是说,我们必然不能在每个 Promise.resolve().then()
的回调中都执行清空队列的操作,而是只能在最后一个中执行。
而这样,又暴露了一个新的问题,那就是创建 Promise.resolve()
的时机:之前是每个 EventLoop 的第一个请求添加到队列的时候执行,而现在变成了每当 batch 队列添加新元素时都应该执行。
我们期盼的场景是这样(假设一次 EventLoop 占用 5 个时间单位,请求回复也需要 5 个时间单位):
于是,我们将发送请求的逻辑迁移到 addRequest
创建 batch 队列的逻辑中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| if (lastIndex + 1 === batchList.length) { batchList.push({ [name]: requestItem }); Promise.resolve().then(() => { const requestMap = batchList[lastIndex + 1]; if (Object.keys(requestMap).length > 1) { sendRequestBatch(requestMap); } else { fetch(name, options).then(success, error); }
if (lastIndex + 2 === batchList.length) { clearRequest(); } }); }
|
同时,我们也可以把累赘的 batchFlag
给去掉。最后,生成的 fetchBatch
简直简得不能再简:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
|
export const fetchBatch = (function anonymousFetch() { let batchList: IRequestMap[] = []; const sendRequestBatch = () => {}; const addRequest = () => {}; const clearRequest = () => {};
return function anonymous( name: string = '', options: IFetchOptions = { data: {} }, success: (obj: IObject) => void = noop, error: (err: Error) => void = noop ) { addRequest({ name, options, success, error }); }; }());
|
试验了一下,就可以在同一个 EventLoop 中发送多次 batch 请求啦~