我们已经实现了自动把同一时刻的请求合并,并统一渲染。
存在问题
老样子,先来看图回顾一下(假设一次 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 队列:
| 12
 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 可调整为如下写法:
| 12
 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 也可以简单调整成这样:
| 12
 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 队列的逻辑中:
| 12
 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 简直简得不能再简:
| 12
 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 请求啦~