实现一个带并发限制的Promise.all
头条的一道面试题~~~ emm 有点意思🤒
✍️业务开发中也有用武之地。
在业务开发中,如用户进入页面,一般会通过http请求像服务端获取数据,而且大多数情况下会有多个http请求。我们一般使用Promise.all
来调用接口,以达到更快速度的拿到数据渲染页面。Promise.all
会在数组中的所有promise达到resolve
状态后,才会去执行.then
的回调。
但是这样也给服务器带来了更大的压力。可以想象一种情况,如果页面上有10w+请求待处理(这里暂不考虑浏览器的请求并发限制),你又只能在拿到所有请求的结果后才能多结果进行处理。这种情况下,大家理所当然的想到了Promise.all
。但是面对洪水般的请求,服务器可能会出现TCP
连接数不足照成等待或拒绝连接。
这个时候我们就需要对Promise.all
进行并发限制。
Promise.all
并发限制指的是如果有10个请求,并发限制是5。那么会在执行的一瞬间发出请求1, 2, 3, 4, 5
,当这5个请求中任意一个成功或失败后,继续请求6
,循环往复,直至10个请求全部完成。然后一次性返回所有请求的结果。从表现上来看和原来的Promise.all
没有任何区别。
🤔大致思路
await
一个promise会暂停当前代码段继续往下执行,直到promise发生改变。Promise.race(promises)
会在promises
中任意一个promise发生状态改变时返回结果。- 如果依次循环所有待执行的请求,将其放入待执行列表中,数量达到并发限制时,使用
await Promise.race
暂停代码段并执行这些请求 Promise.race
状态发生改变,继续循环,直至执行完所有请求。
读到这里是否有思路了呢?如果有,那就自己先写着试试。看答案和思考得到的结果记忆深度是不一样的哦~~
😏代码实现
- 定义两个变量,
array
表示待执行的函数列表,limit
表示并发限制 - 遍历待执行的函数数组
- 从
array
第1个元素开始,初始化promise
对象,同时用一个executing
数组保存正在执行的promise,并将其存放于ret
中 - 循环初始化promise,直到达到
limt
- 使用
await Promise.race
,暂停for
循环,并执行executing
中的promise。 - 当
executing
中有任意一个promise执行完毕,就继续for
循环,不断初始化promise并放入executing
中,知道达到limit
- 当所有promise都执行完了,调用
Promise.all
返回
- 从
async function asyncPool(array, limit) {
// 存储Promise的执行结果
const ret = []
// 正在执行的promise
const executing = []
// 遍历待执行的函数列表
for (const item of array) {
// promise包装并执行当前待执行函数
const p = Promise.resolve().then(() => item(1))
// 将函数存至ret结果列表中(p此时是 Promise { <pending> })
ret.push(p)
if (limit <= array.length) {
// 在p.then执行的时候说明p的状态已经发生了翻转(resolve || reject),
// 此时将executing列表删除一个
p.then(() => executing.splice(0, 1))
// 将p存放至executing中
executing.push(p)
// executing内的待执行数量大于等于limit的时候,使用await Promise.race暂停for循环,
// executing中任意一个promise发生状态改变,就会继续循环
if (executing.length >= limit) await Promise.race(executing)
}
}
return Promise.all(ret)
}
asyncPool([], 10).then(result => console.log(result))
😌测试用例
const delay = s => new Promise(resolve => setTimeout(resolve, s * 1000))
async function request(index) {
console.log(`Request: ${index}`)
await delay(1)
console.log(`Response: ${index}`)
return index
}
const pool = [...new Array(50).keys()].fill(request)
asyncPool(pool, 10).then(result => console.log(result))
😌总结
这个面试题主要考了多Promise
的理解与运用,网上有很多个版本的实现,递归调用的居多。emmm...我不喜欢递归,所以没用递归。
温故而知新,如果记得不是很深刻,建议Ctrl + D
收藏到书签,下次访问更便捷。