先看图来回顾一下(假设一次 EventLoop 占用 5 个时间单位,请求回复也需要 5 个时间单位):
我们已经实现了自动把同一时刻的请求合并,并统一渲染。
但写完之后,发现很多写法都比较 low,所以对代码做了一次整体重构。
TS 化
首先,我们把之前涉及到的对象都用 typescript 定义,以方便后期维护:
1 2 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
的请求参数。
但是这样子存,感觉过于分散,其实可以耦合成一个对象:
1 2 3 4 5 6 7 8 9
|
type IRequestItem = { name: string; data: IObject; success: (obj: IObject) => void; error: (err: Error) => void; };
|
然后把多个请求组合成一个新的 Map 对象,同时在 addRequest
中添加如下处理:
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 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
中添加如下处理:
1 2 3 4 5 6
|
const clearRequest: () => void = () => { requestMap = {}; };
|
然后如下方式转化成 batch
请求的参数:
1 2 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
,使得逻辑更为清晰:
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 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 函数啦:
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 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; }; })();
|
可以见得,重构后的代码,精简了很多,逻辑也清晰了很多。
重构到此就初步大功告成啦!~ (^▽^)