先看图来回顾一下(假设一次 EventLoop 占用 5 个时间单位,请求回复也需要 5 个时间单位):

我们已经实现了自动把同一时刻的请求合并,并统一渲染。
但写完之后,发现很多写法都比较 low,所以对代码做了一次整体重构。
TS 化
首先,我们把之前涉及到的对象都用 typescript 定义,以方便后期维护:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 
 | 
 
 export type IObject = Record<string | number | symbol, any>;
 
 
 
 
 interface IResponse {
 code: number;
 data: IObject;
 message?: string;
 msg?: string;
 }
 
 
 
 
 interface IErrorResponse extends Error {
 code?: number;
 }
 
 | 
拆分代码功能
原有的逻辑是把所有功能耦合在一次顺序执行,但实际上整个批量请求的流程可以分为:
- 添加请求到队列中
- 发送请求
- 清空请求队列
- 处理请求回调
注:处理请求回调是异步执行的,而清空请求队列是每个 EventLoop 发送完请求都会执行一遍的,所以实际发生的时间是:清空在前,处理回调在后。
这其中,第 2 和 第 3 就可以抽成 addRequest 和 clearRequest 方法。
合并松散对象
原先的逻辑是,根据请求 url 作区分,分别用三个对象存请求参数、成功和错误回调的信息,这样存的好处是 paramsMap 可以直接作为 /api/batch 的请求参数。
但是这样子存,感觉过于分散,其实可以耦合成一个对象:
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | 
 
 type IRequestItem = {
 name: string;
 data: IObject;
 success: (obj: IObject) => void;
 error: (err: Error) => void;
 };
 
 | 
然后把多个请求组合成一个新的 Map 对象,同时在 addRequest 中添加如下处理:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 
 | 
 
 type IRequestMap = {
 [url: string]: IRequestItem;
 };
 
 let requestMap: IRequestMap = {};
 
 
 
 
 
 const addRequest: (requestItem: IRequestItem) => void = (requestItem) => {
 const { name, data, success, error } = requestItem;
 requestMap[name] = {
 name,
 data: deepStringify(data),
 success,
 error
 };
 };
 
 
 addRequest({
 name,
 data: deepStringify(params.data),
 success: params.success ?? success,
 error: params.error ?? error
 });
 
 | 
以及在 clearRequest 中添加如下处理:
| 12
 3
 4
 5
 6
 
 | 
 
 const clearRequest: () => void = () => {
 requestMap = {};
 };
 
 | 
然后如下方式转化成 batch 请求的参数:
| 12
 3
 4
 5
 6
 7
 
 | const batchData = Object.values(requestMap).reduce((pre: IObject, cur: IRequestItem) => {
 const { name: requestName, data } = cur;
 return { ...pre, [requestName]: data };
 },
 {}
 );
 
 | 
以及封装下发送批量请求的函数 sendRequestBatch,使得逻辑更为清晰:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 
 | 
 
 type IBatchResponse = {
 [url: string]: IResponse;
 };
 
 
 
 
 
 const sendRequestBatch: (tempRequestMap: IRequestMap) => void = (
 tempRequestMap
 ) => {
 
 const batchData = {};
 
 
 fetchBase('/api/batch', {
 data: batchData
 })
 .then((res: IBatchResponse) => {
 
 unstable_batchedUpdates(() => {
 Object.keys(res).forEach((key) => {
 const { code, message, msg, data } = res[key];
 const {
 success: successCallback,
 error: errorCallback
 } = tempRequestMap[key];
 if (code !== 200) {
 const err: IErrorResponse = new Error(
 (message || msg) ?? '系统繁忙,请稍后再试'
 );
 err.code = code;
 if (errorCallback) {
 errorCallback(err);
 }
 } else if (successCallback) {
 successCallback(data);
 }
 });
 });
 })
 .catch((err: Error) => {
 
 Object.keys(tempRequestMap).forEach((key) => {
 const { error: errorCallback } = tempRequestMap[key];
 if (errorCallback) {
 errorCallback(err);
 }
 });
 });
 };
 
 | 
最后删除 count,直接用 requestMap 的 keys 数量做判断,就生成了最后的 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
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 
 | 
 
 interface IFetchOptions {
 data: IObject;
 }
 
 
 
 
 const noop = () => {};
 
 
 
 
 export const fetchBatch = (function anonymousFetch() {
 let batchFlag: Promise<void> | null = null;
 let requestMap: IRequestMap = {};
 
 const sendRequestBatch = () => {};
 const addRequest = () => {};
 const clearRequest = () => {};
 
 
 return function anonymous(
 name: string = '',
 options: IParams = { data: {} },
 success: (obj: IObject) => void = noop,
 error: (err: Error) => void = noop
 ) {
 
 if (requestMap[name]) {
 return fetch(name, options).then(success, error);
 }
 
 
 addRequest({
 name,
 data: deepStringify(options.data),
 success,
 error
 });
 
 
 if (!batchFlag) {
 
 batchFlag = Promise.resolve().then(() => {
 
 if (Object.keys(requestMap).length > 1) {
 sendRequestBatch(requestMap);
 } else {
 
 return fetch(name, options).then(success, error);
 }
 
 
 clearRequest();
 });
 }
 
 return null;
 };
 })();
 
 | 
可以见得,重构后的代码,精简了很多,逻辑也清晰了很多。
重构到此就初步大功告成啦!~ (^▽^)