如何实现一个 fetch 请求池?

介绍

大家好,我是大胆的番茄

请求池是前端面试当中经常会问的一道题目:如何保证同时只有 N 个请求在发送,成功一个就再请求一个?

首先我们要理解为啥要设计一个请求池?主要还是从性能和浏览器并发来考量。浏览器设计当初就定义了浏览器打开页面,同时发送 http 请求的瞬时数量:

浏览器 HTTP 1.1 HTTP 1.0
Chrome 6 6
Firefox 6 6
Safari 4 4

从上表可以看出,Chrome 最大可以同时发送 6 个请求,超过之后会有明显的等待时间。

所以我们从面试题开始看如何实现请求池,设计一个请求池,让下面的代码可以正常运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
const fetchPool = new FetchPool({
max: 3,
});

for (let i = 0; i < 10; i++) {
fetchPool('https://uquuu.com/yesorno')
.then((value) => {
console.log('value :>> ', i, value);
})
.catch((error) => {
console.log('error :>> ', i, error);
});
}

废话不多说,上代码。

实现

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
class FetchPool {
// 默认配置
options = {
// 最大同时请求数量
max: 6,
};
// 队列(待请求的任务列表)
queues = [];
// 计数(正在请求中的数量)
count = 0;

constructor(options) {
this.options = { ...this.options, ...options };
/**
* 返回 fetch 函数
*
* @see https://developer.mozilla.org/zh-CN/docs/Web/API/fetch
*/
return (input, init) => {
// fetch 返回一个 promise
return new Promise((resolve, reject) => {
// 将这次请求的 input/init,promise 的 resolve/reject 放入队列,等待处理
this.queues.push({
input,
init,
resolve,
reject,
});
// 处理队列
this.runQueues();
})
}
}

runQueues() {
// 判断队列中是否还有未请求的任务,没有则返回
if (this.queues.length === 0) return;
// 判断正在请求中的数量是否大于等于最大请求数,是则返回
if (this.count >= this.options.max) return;

// 将正在请求中的数量 +1
this.count++;
// 从队列中取出一个任务进行处理
const queue = this.queues.shift();
// 处理请求
fetch(queue.input, queue.init).then((res) => {
// 处理结束,将正在请求中的数量 -1
this.count--;
// 继续处理队列
this.runQueues();
// 返回请求结果
return queue.resolve(res);
}).catch((err) => {
// 处理结束,将正在请求中的数量 -1
this.count--;
// 继续处理队列
this.runQueues();
// 返回请求结果
return queue.reject(err);
})
}
}