简介
在开发过程中,我们经常需要处理多个并发请求。并发请求可以提高系统的性能,但也需要注意控制并发数,避免过多请求导致性能问题。
两种处理方式
可以使用以下两种方式来处理并发请求:
方式一
function sendRequest(requestList, limits, callback) {
// 参数说明
// requestList:请求列表。
// limits:最大并发数,默认为 3。
// callback:请求完成后的回调函数,参数为一个包含所有请求结果的数组。
// 1. 将请求列表浅拷贝一份,用于存储未执行的请求。
const promises = requestList.slice()
// 2. 计算最大并发数,取规定并发数与请求数中最小的数。
const concurrentNum = Math.min(limits, requestList.length)
// 3. 初始化一个变量,用于记录当前并发数。
let concurrentCount = 0
// 4. 启动当前能执行的任务。
const runTaskNeeded = () => {
let i = 0
// 启动当前能执行的任务
while (i < concurrentNum) {
i++
runTask()
}
}
// 5. 取出任务并且执行任务。
const runTask = () => {
// 删除并返回第一个元素
const task = promises.shift()
// 执行任务
task && runner(task)
}
// 6. 执行器
// 执行任务,同时更新当前并发数
const runner = async (task) => {
try {
// 并发数+1
concurrentCount++
// 执行任务
await task()
} catch (error) {
// 错误时处理逻辑
} finally {
// 并发数-1
concurrentCount--
// 捞起下一个任务
picker()
}
}
// 7. 捞起下一个任务
const picker = () => {
// 任务队列里还有任务并且此时还有剩余并发数的时候 执行
if (concurrentCount < limits && promises.length > 0) {
// 继续执行任务
runTask()
// 队列为空的时候,并且请求池清空了,就可以执行最后的回调函数了
} else if (promises.length == 0 && concurrentCount == 0) {
// 执行结束
callback && callback()
}
}
// 8. 入口执行
runTaskNeeded()
}
方式二
async function sendRequest(requestList, limits, callback) {
// 参数说明
// requestList:请求列表。
// limits:最大并发数,默认为 3。
// callback:请求完成后的回调函数,参数为一个包含所有请求结果的数组。
// 1. 初始化一个 promise 队列,用于存储所有请求的 promise。
const promises = []
// 2. 初始化一个 Set 集合,用于存储当前正在执行的请求。
const pool = new Set() // set也是Iterable<any>[]类型,因此可以放入到race里
// 3. 循环执行以下步骤:
// 从请求列表中取出一个请求。
// 如果当前并发数未达到限制,则执行该请求。
// 将该请求加入并发池。
for (let request of requestList) {
// 开始执行前,先await 判断 当前的并发任务是否超过限制
if (pool.size >= limits) {
// 这里因为没有try catch ,所以要捕获一下错误,不然影响下面微任务的执行
await Promise.race(pool)
.catch(err => err)
}
const promise = request() // 拿到promise
// 删除请求结束后,从pool里面移除
const cb = () => {
pool.delete(promise)
}
// 注册下then的任务
promise.then(cb, cb)
pool.add(promise)
promises.push(promise)
}
// 4. 等最后一个for await 结束,这里是属于最后一个 await 后面的 微任务
// 注意这里其实是在微任务当中了,当前的promises里面是能确保所有的promise都在其中(前提是await那里命中了if)
Promise.allSettled(promises).then(callback, callback)
}
对比
两种处理方式的共同点是:
-
都可以限制最大并发数,避免过多请求导致性能问题。
-
都可以按照请求列表的顺序返回请求结果。
两种处理方式的区别是:
-
第一种处理方式使用循环来执行请求,因此当请求列表很大时,可能会导致内存占用过多。
-
第二种处理方式使用 Set 集合来存储当前正在执行的请求,避免了内存占。
如果觉得文章还行,可以留下你的一个小小的👍。