面试题:写一个方法,实现最大并发数的并发请求

2,940 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第3天,点击查看活动详情

前因

最近周末闲着无事,在掘金上刷着沸点,刷着文章,突然看到这么一个面试题

实现一个方法multiRequestLimitNum(reqArr, limitNum),这个方法有以下功能

  1. 可以并发发请求
  2. 但是并发的请求数有限制,不能超过limitNum。
  3. 并发的请求每成功一个,就可以从reqArr中取一个去补上,满足最大并发数limitNum。
  4. 最后返回值需要按照reqArr的顺序返回。

分析

一开始看到并发发请求,就会想到Promise.all,它可以实现请求的并发,但是它并不能控制最大并发数。

而且它要成功一个,就要补上一个。目前Promise.all应该是做不到的,只能另辟蹊径。

现有的api都无法满足,那只能手写方法来实现。

第一步

我们先判断当前reqArr的长度是否大于最大并发数。如果大于则无需处理,如果小于则最大并发数是reqArr的长度。

然后遍历,我这里使用while循环,定义个索引i等于0,然后i++,每次都执行请求函数,直到i等于最大并发数。

这样模拟并发。

第二步

我们的i++是在请求函数里面执行的,把当前索引赋值cur变量,执行reqArr对应i索引的函数,

把返回值赋值给一开始定义的reqArr长度的数组resArr。

但是执行函数是异步的,这时候i是已经变化了,可以通过前面赋值的cur变量来把返回值放到resArr对应的位置。

这样子可以保证返回的顺序是按照reqArr的顺序。

第三步

如果此时 i 还不等于reqArr的长度,则递归调用请求函数,这样就满足成功一个请求,就补上一个请求。

直到i等于reqArr的长度的时候,则代表reqArr的请求已经完成,把数组resArr resolve返回即可。

代码如下:

function multiRequestLimitNum (reqArr, limitNum) {
    const reqLen = reqArr.length
    const resArr = new Array(reqLen)
    let i = 0
    return new Promise((resolve, reject) => {
        const maxNum = reqLen >= limitNum ? limitNum : reqLen
        while (i < maxNum) {
            reqFn()
        }
        async function reqFn () {
            if (i > reqLen - 1) return
            const cur = i++
            const fn = reqArr[cur]
            const data = await fn().catch((err) => { return err })
            resArr[cur] = data
            // 不用length判断,因为resArr里面是empty,用Object.values
            // 这里之前用的 i 判断,是不准确的,假设最大并发数是1,就会导致reqArr只会执行第一个,需要改成reqLen判断,感谢评论区大佬指正。
            if (reqLen === Object.values(resArr).length) resolve(resArr)
            else reqFn()
        }
    })
}

我们用上面的代码来验证一下,用setTimeout来模拟发出的请求

function req (res, delay) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(res)
    }, delay)
  })
}
multiRequestLimitNum([
  req.bind(null, 1, 1000),
  req.bind(null, 2, 3000),
  req.bind(null, 3, 2000),
  req.bind(null, 4, 100)],
  2)

image.png

通过图片可以看到,我们传入函数的延时都是不一样的,但是打印出来的结果是按照传入的顺序打印的。

并且并发的请求是成功一个,才会取下个请求去请求。

大家可以试试。

大家如果有其它方法欢迎评论,一起沟通。