💡问题出现 -> 要同时发100个请求
我们经常需要
处理大量API
请求。然而,浏览器对同一域名下的并发请求数量有限制(通常是
6-8
个),
直接发送大量请求
可能会导致性能问题
甚至服务器过载
。
当我们需要发送几十甚至上百个请求时,如果一次性全部发出:
-
浏览器
会限制
实际并发数量,导致请求排队
-
低配
置服务器可能因突然的高负载
而宕机
-
带宽有限
的服务器可能响应变慢
用这种方式模拟发100个请求
:
js
const ids = new Array(100).fill('')
console.time()
for (let i = 0; i < ids.length; i++) {
console.log(i) // 模拟发送请求
}
console.timeEnd()
💡 确定思想 -> 控制一批一批来
维护一个请求池(队列)
控制同时进行的请求数量(并发数)
当一个请求完成时,自动从队列中取出下一个请求
何为队列
队列是一种先进先出(FIFO)
的数据结构,类似于现实生活中的排队。
新请求
被添加到
队列末尾
(入队),而处理请求
时从
队列头
部取
出(出队)。
代码
js
export const handQueue = (reqs) => {
reqs = reqs || []
const requestQueue = (concurrency) => {
concurrency = concurrency || 6 // 默认最大并发数
const queue = [] // 请求池
let current = 0 // 当前并发数
// 出队函数,处理实际请求
const dequeue = () => {
while (current < concurrency && queue.length) {
current++
const requestPromiseFactory = queue.shift() // 从队列头部取出请求
requestPromiseFactory()
.then(() => {
// 请求成功处理逻辑
})
.catch(error => {
console.log(error) // 错误处理
})
.finally(() => {
current-- // 释放一个并发位置
dequeue() // 尝试处理下一个请求
})
}
}
// 返回入队函数
return (requestPromiseFactory) => {
queue.push(requestPromiseFactory) // 将请求加入队列
dequeue() // 尝试处理请求
}
}
// 创建并发数为6的队列
const enqueue = requestQueue(6)
// 将所有请求加入队列
for (let i = 0; i < reqs.length; i++) {
enqueue(() => fetch.get('/api/test' + i))
}
}
代码解析
-
handQueue函数:主函数,接收请求数组
-
requestQueue函数 :创建请求队列,设置最大并发数
concurrency
:最大并发数,默认为6
queue
:存储待处理请求的队列
current
:当前正在处理的请求数 -
dequeue函数 :核心处理逻辑 当当前并发数
小于最大值
且队列不为空
时,处理请求从队列中取出请求并执行
无论请求成功或失败,最终都会
减少当前并发数
并尝试处理下一个请求
-
入队函数:将新请求加入队列并尝试处理
只有有请求响应成功的同时才会有新的请求进来,极大的降低了服务器的压力。
💡 用库也行
生产环境推荐库
p-limit
javascript
import pLimit from 'p-limit';
const limit = pLimit(10);
const results = await Promise.all(
requestList.map(request =>
limit(() => request())
)
);
async-pool
javascript
import asyncPool from 'tiny-async-pool';
const results = await asyncPool(10, requestList, requestFn);
💡 讨论
思考🤔
问:为什么不用Promise.all
?
错误处理缺陷:一错全错
- 问题 :
Promise.all
会在一组 Promise 中任意一个失败时立即拒绝,导致整组请求被丢弃。 - 后果:即使其他 9 个请求成功,只要第 10 个失败,整组结果都无法获取。
javascript
Promise.all([request1, request2, request3])
.then((results) => console.log(results)) // 全部成功才触发
.catch((error) => console.error(error)); // 任意一个失败直接进入这里
思考🤔
那用Promise.allSettled
行不行,可不可取?
它是可以失败的和成功的都等,但是,如果当中一个请求特慢,就会堵住后面的接口并发。