本文节选自我的博客:
前端如何并发控制
- 💖 作者简介:大家好,我是MilesChen,偏前端的全栈开发者。
-
📝 CSDN主页:
爱吃糖的猫
🔥 -
📣 我的博客:
爱吃糖的猫
-
📚 Github主页:
MilesChen
- 🎉 支持我:点赞👍+收藏⭐️+留言📝
- 💬介绍:The mixture of WEB+DeepLearning+Iot+anything🍁
前言
众所周知,Promise处理异步任务能避免他们阻塞程序执行。当一次并发大量异步任务会导致内存消耗过大、程序阻塞等问题。本文带大家实现异步任务控制器,限制并发异步任务数量,来解决高并发问题。
假设一个场景:有20个异步任务,每次只能处理三个异步任务,要求尽可能快速的拿到处理结果。
下面带来分段
Promise.all
和
异步任务控制器
两种实现方案。
Promise.all
Promise.all
暴力
Promise.all
Promise.all
最简单的方式就是
Promise.all
,一次并发20个任务,没有使用异步任务控制,简单、粗暴。
// 模拟请求 随机产生100-500ms延时
function randomRequest(url){
return new Promise((resolve)=>{
let delay = Math.floor(Math.random()*400+100)
setTimeout(()=>{
resolve({state:'success',data:{url}})
},delay)
})
}
async function main(){
const queue = [];
for (let i = 1; i <= 20; i++) {
queue.push(randomRequest(`https://xxx.xx/api/${i}`));
}
let a = await Promise.all(queue)
console.log(a);
}
mian()
分段
Promise.all
Promise.all
这种分段的方式,有两个显著的缺陷:
- 阻塞问题:因为程序每次都要等异步任务全执行完,才进行下个异步任务,其中一个异步任务发生阻塞则会导致整体阻塞。
- 无法处理reject: 一旦有一个 Promise 被拒绝就立即返回拒绝的 Promise,并不会等待其他 Promise 的解析结果
// 模拟请求 随机产生100-500ms延时
function randomRequest(url){
return new Promise((resolve)=>{
let delay = Math.floor(Math.random()*400+100)
setTimeout(()=>{
resolve({state:'success',data:{url}})
},delay)
})
}
async function main(maxNum){
const queue = [];
for (let i = 1; i <= 20; i++) {
queue.push(randomRequest(`https://xxx.xx/api/${i}`));
}
for(let i=0;i<Math.ceil(queue.length/maxNum);i++){
let a = await Promise.all(queue.slice(i*maxNum,i*maxNum+maxNum))
console.log(a);
}
}
main(3)
异步任务控制器
开始就并发3个数量的一次任务,当一个异步任务处理完成,接龙下个异步任务,就像3条流水线并行。解决了
Promise.all
带来的阻塞问题和无法处理reject问题。
实现需要注意:
- urls的长度为0时,results就没有值,此时应该返回空数组
- maxNum大于urls的长度时,应该取的是urls的长度,否则则是取maxNum
- 需要定义一个count计数器来判断是否已全部请求完成
- 因为没有考虑请求是否请求成功,所以请求成功或报错都应把结果保存在results集合中
- results中的顺序需和urls中的保持一致
// 模拟请求 0.5概率成功,随机产生100-500ms延时
function randomRequest(url){
return new Promise((resolve,reject)=>{
let delay = Math.floor(Math.random()*400+100)
setTimeout(()=>{
let rand = Math.random()
if(rand>0.5) resolve({state:'success',data:{url}})
else reject({state:'error'})
},delay)
})
}
// 并发控制函数
const controlAsync = (urls, maxNum) => {
return new Promise((resolve) => {
if (urls.length === 0) {
resolve([]);
return;
}
const results = [];
let index = 0; // 下一个请求的下标
let count = 0; // 当前请求完成的数量
// 发送请求
async function request() {
if (index === urls.length) return;
const i = index; // 保存序号,使result和urls相对应
const url = urls[index];
index++;
console.log(url);
try {
const resp = await randomRequest(url);
// resp 加入到results
results[i] = resp;
} catch (err) {
// err 加入到results
results[i] = err;
} finally {
count++;
// 判断是否所有的请求都已完成
if (count === urls.length) {
console.log('完成了');
resolve(results);
}
request();
}
}
// maxNum和urls.length取最小进行调用
const times = Math.min(maxNum, urls.length);
for(let i = 0; i < times; i++) {
request();
}
})
}
const urls = [];
for (let i = 1; i <= 20; i++) {
urls.push(`https://xxx.xx/api/${i}`);
}
controlAsync(urls, 3).then(res => {
console.log(res);
})
总结
异步任务控制器
比
Promise.all
实现复杂一些,但能解决阻塞问题和reject问题;
在工作中可以将
异步任务控制器
封装成通用的工具函数,实现多种异步任务的并发控制。
感谢小伙伴们的耐心观看,本文为笔者个人学习记录,如有谬误,还请告知,万分感谢!如果本文对你有所帮助,还请点个关注点个赞~,您的支持是笔者不断更新的动力!