Orion's Studio.

自动批量请求(4)—— 删减重复的请求

2022/04/15

我们已经实现了自动把同一时刻的请求合并成多个 fetch,并统一渲染。

存在问题

但是又遇到了一种新的场景:就是不同组件,发送相同参数的相同请求时,这在原来的场景下,还是会发两个相同的请求。

而想要优化,只能把请求移到外层,通过参数分别透传给两个组件。

那,我们这个 fetch 组件能不能直接把这个工作给做了呢,让用户自由地发送请求,而由 fetch 组件来做优化呢?

具体实现

我们来思考一下,这个思路,就是把请求删减为一个,而把成功处理和失败回调变成多个,就可以啦。

细节上来说的话,就是遇到同名且相同参数的请求时,原本会作增加新请求操作,而现在直接跳过,同时把回调函数加到对应的成功及失败回调中。

那我们先来第一步:

回调变为数组

首先是增加一些新的定义:

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
/**
* 成功回调
*/
type ISuccessCallback = (obj: IObject) => void;

/**
* 失败回调
*/
type IErrorCallback = (err: Error) => void;

/**
* batch 队列的单元数据信息
*/
interface IBatchItem {
name: string; // 请求 url
options: IFetchOptions; // fetch 的 options 参数
successList: ISuccessCallback[]; // 成功回调列表
errorList: IErrorCallback[]; // 错误回调列表
}

/**
* 同一个队列多个请求组成的 Map,键名是 api
*/
interface IBatchMap {
[url: string]: IBatchItem; // 每个请求对应的元数据信息
}

然后便是处理这些逻辑的地方:

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
64
65
66
67
68
69
70
let batchList: IBatchMap[] = []; // 多个batch请求组成的数组

/**
* 发送批量请求
* @param tempRequestMap 临时缓存的变量,在执行请求回调时,原来的 requestMap 其实已经清空了
*/
const sendRequestBatch: (tempRequestMap: IBatchMap) => void = (
tempRequestMap
) => {
// 转化为 batch 请求需要的格式
const batchData = Object.values(tempRequestMap).reduce(
(pre: IObject, cur: IBatchItem) => {
const { name: requestName, options } = cur;
return { ...pre, [requestName]: deepStringify(options.data) }; // 深度 stringify 对象,batch 请求需要
},
{}
);

// 调用批处理请求
fetchBase('/api/batch', {
data: batchData
})
.then((res: IBatchResponse) => {
// 用 unstable_batchedUpdates 统一更新,减少渲染次数
withBatch(() => {
Object.keys(res).forEach((key) => {
const { code, message, msg, data } = res[key];
if (!tempRequestMap[key]) {
return;
}
const {
successList: successCallbackList,
errorList: errorCallbackList
} = tempRequestMap[key];
if (code !== 200) {
const err: IErrorResponse = new Error(
(message || msg) ?? '系统繁忙,请稍后再试'
);
err.code = code;
errorCallbackList.forEach(
(errorCallback: IErrorCallback) => {
if (errorCallback) {
errorCallback(err);
}
}
);
} else {
successCallbackList.forEach(
(successCallback: ISuccessCallback) => {
if (successCallback) {
successCallback(data);
}
}
);
}
});
});
})
.catch((err: Error) => {
// 如果 batch 请求报错了,则给每个请求都返回 batch 请求的报错
Object.keys(tempRequestMap).forEach((key) => {
const { errorList: errorCallbackList } = tempRequestMap[key];
errorCallbackList.forEach((errorCallback: IErrorCallback) => {
if (errorCallback) {
errorCallback(err);
}
});
});
});
};

跳过同参数请求

第二步便是在 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
31
32
33
34
35
36
37
38
39
40
41
42
/**
* 增加请求到队列中
* @param requestItem 单个请求信息
*/
const addRequest: (requestItem: IRequestItem) => void = (requestItem) => {
const { name, options, success, error } = requestItem;

// 寻找相同参数的相同请求,通过 name 和序列化的请求参数进行比较
const stringifyData = JSON.stringify(options?.data);
const equalIndex = Math.max(
-1,
...batchList.map((item: IBatchMap, index: number) =>
item[name] &&
JSON.stringify(item[name].options?.data) === stringifyData
? index
: -1
)
);
if (equalIndex > -1) {
batchList[equalIndex][name].successList.push(success);
batchList[equalIndex][name].errorList.push(error);
return;
}

// batch 元数据
const batchItem = {
name,
options,
successList: [success],
errorList: [error]
};

// 查找最后一个含有对应 name 的请求对象,查找不到时默认赋值为 -1,这样当队列为空即数组长度为0时,lastIndex + 1 也为 0
const lastIndex = Math.max(
-1,
...batchList.map((item: IBatchMap, index: number) =>
item[name] ? index : -1
)
);

/* 后略 */
};
CATALOG
  1. 1. 存在问题
  2. 2. 具体实现
    1. 2.1. 回调变为数组
    2. 2.2. 跳过同参数请求