前言
众所周知,Promise处理异步任务能避免他们阻塞程序执行。当一次并发大量异步任务会导致内存消耗过大、程序阻塞等问题。本文带大家实现异步任务控制器,限制并发异步任务数量,来解决高并发问题。
假设一个场景:有20个异步任务,每次只能处理三个异步任务,要求尽可能快速的拿到处理结果。
下面分为Promise.all
和异步任务控制器
两种实现方案。
Promise.all
暴力Promise.all
最简单的方式就是Promise.all
,一次并发20个任务,没有使用异步任务控制,简单、粗暴。
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
这种分段的方式,有两个显著的缺陷:
- 阻塞问题:因为程序每次都要等异步任务全执行完,才进行下个异步任务,其中一个异步任务发生阻塞则会导致整体阻塞。
- 无法处理reject: 一旦有一个 Promise 被拒绝就立即返回拒绝的 Promise,并不会等待其他 Promise 的解析结果
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中的保持一致
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; const url = urls[index]; index++; console.log(url); try { const resp = await randomRequest(url); results[i] = resp; } catch (err) { results[i] = err; } finally { count++; if (count === urls.length) { console.log('完成了'); resolve(results); } request(); } } 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问题;
在工作中可以将异步任务控制器
封装成通用的工具函数,实现多种异步任务的并发控制。
感谢小伙伴们的耐心观看,本文为笔者个人学习记录,如有谬误,还请告知,万分感谢!如果本文对你有所帮助,还请点个关注点个赞~,您的支持是笔者不断更新的动力!