头条的一道面试题~~~ 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没有任何区别。

🤔大致思路

  1. await一个promise会暂停当前代码段继续往下执行,直到promise发生改变。
  2. Promise.race(promises)会在promises中任意一个promise发生状态改变时返回结果。
  3. 如果依次循环所有待执行的请求,将其放入待执行列表中,数量达到并发限制时,使用await Promise.race暂停代码段并执行这些请求
  4. Promise.race状态发生改变,继续循环,直至执行完所有请求。

读到这里是否有思路了呢?如果有,那就自己先写着试试。看答案和思考得到的结果记忆深度是不一样的哦~~

😏代码实现

  1. 定义两个变量,array表示待执行的函数列表,limit表示并发限制
  2. 遍历待执行的函数数组
    1. array第1个元素开始,初始化promise对象,同时用一个executing数组保存正在执行的promise,并将其存放于ret
    2. 循环初始化promise,直到达到limt
    3. 使用await Promise.race,暂停for循环,并执行executing中的promise。
    4. executing中有任意一个promise执行完毕,就继续for循环,不断初始化promise并放入executing中,知道达到limit
    5. 当所有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收藏到书签,下次访问更便捷。