2024-07-01 · 6 min read
日常开发中的并发请求,一般不会去特地控制,直接依次请求就好,或者使用 Promise.all()
包装一层,等待所有成功的结果。但是当并发请求的数量大起来后,如果不处理并发量,可能会造成较严重的后果。比如大文件上传的场景,一般大文件上传需要切片分传,如果文件较大,可能会被分成100个小块分别请求,那这种同时100条的并发请求,大概率会有以下几个问题:
所以我们需要对大并发请求做个控制,即限制每次并发的请求量,比如每次只请求5条,100条请求分20次完成。那么怎么实现这种效果呢?
我看好多人会用 Promise.all()
来处理,比如将100条异步请求分成20个数组,每个数组有5个请求,每次将一个请求数组传入 Promise.all()
中,当这组数据请求完,再传入下一个数组,以此类推直至请求结束。这种做法在理论上确实可以解决并发问题,之所以是理论上,主要有两个问题:
Promise.all()
不能保证所有的请求结束后再给出结束状态,一旦有一个请求错误,会直接返回错误信息,不管后面的成员还在不在请求中。这样本质上还是无法准确控制同时并发量;Promise.allSettled
,保证所有的请求结束后才返回状态。那么还是有效率问题,因为它需要等待这组数据全部请求完才能开启下一轮,如果这一组数据有一个接口需要10秒,那么这组数据请求就需要多等10秒,显然这问题是很大的。所以最终还是需要我们自己手动来封装一个并发请求控制,主要实现两个功能:
最后,根据需求,参考网上资料,我简单封装了一个并发请求方法,代码如下:
function concurRequest(urls, maxNum) {
return new Promise((resolve) => {
if (urls.length === 0) {
resolve([])
}
// 请求的 url 索引
let index = 0
// 已完成请求的数量
let count = 0
// 请求到的结果,每项都是 promise
const result = []
// 请求函数
async function request() {
// 缓存原始的下标
const i = index
// 当前要请求的 url
const url = urls[index]
// 获取完当前的请求地址后,递增下标,为下一次请求准备
index++
try {
const res = await fetch(url)
result[i] = res
} catch (err) {
result[i] = err
} finally {
count++
// 如果已请求的数量等于总请求数量,那么就返回结果
if (count === urls.length) {
resolve(result)
}
if (index < urls.length) {
request()
}
}
}
// 第一次触发请求
for (let i = 0; i < Math.min(maxNum, urls.length); i++) {
request()
}
})
}
concurRequest
方法可以传入两个参数,接口请求路径数组urls
和并发数量maxNum
,方法会按照传入的最大请求数来控制同时并发量,当某一个请求结束后,会按照接口数组中的顺序自动请求下一条,直至结束。当然这只是一个范例,我们可以根据实际开发需求改动,比如可以传入一组定义好的请求方法,这样每个请求方法的内部都可以灵活控制。