记一道控制并发数的前端面试题【手动维护 HTTP 请求排队】

admin 2019年03月13日 查看105次

题目

思路分析

不知大家是否还记得,微信小程序刚开高端网站建设始的时候,有一个并发请求限制,同时发出的请求数若超出这个限制则将被残忍抛弃(现在不会了)。由此催生了很多开发者在自己的项目中造了「请求排队」的轮子。

这是一个简化版本的「请求排队」。具体实现如下:

  • 首先我们需要维护一个任务队列、当前正在运行的线程计数、结果数组
  • 将请求推送到队列中,同时线程计数 +1
  • 当运行线程数小于最大线程限制时,读取任务队列中的首项并执行,将执行结果存放在结果数组中
  • 执行完一条后,将正在运行的数目 -1
  • 当队列为空时,表明无任务将执行,此时执行回调函数,并将结果数组作为参数传入到回调函数中

必要的异常处理:

  • urls 请求数组为空或不为数组,直接抛错
  • max 请求最大限制不大于0,则设置为 urls 的总数

// 任务队列
class TaskQuene {
    constructor(concurrency) {
        this.concurrency = concurrency
        this.running = 0
        this.quene = [] // 任务队列
        this.results = []
        this.callback = null
    }
    pushTask(task) {
        this.quene.push(task)
        this.next()
    }
    next() {
        while (this.running < this.concurrency && this.quene.length) {
            const task = this.quene.shift()
            // 捕获请求返回值
            task().then((resolve) => {
                this.results.push({ resolve })
            }).catch((reason) => {
                this.results.push({ reason })
            }).finally(() => {
                this.running--
                this.next()
            })
            this.running++
        }
        
        // 执行回调函数
        if (typeof callback === "function" && this.running == 0) {
            callback(this.results)
        }
    }

}

function <深圳网页设计公司span class="hljs-title">sendRequest(urls, max, callback) {
    
    // 简单的错误处理 urls 非数组抛错
    if (!(urls instanceof Array) || urls.length === 0) {
        throw Error(`urls`)
    }
    
    // 最大请求数目限制
    if (!(max > 0)) {
        max = urls.length
    }
    
    // 定义一个任务队列
    const downloadQueue = new TaskQuene(max)
    downloadQueue.callback = callback

    urls.forEach(link => {
        let task = () => {
            return fetch(link)
            // ...catch()
        }
        downloadQueue.pushTask(task)
    });
}

复制代码

执行效果

网页制作

让我们用掘金的地址来测试下(掘金的工作人员不要打我😀):

最大限制为 5,urls 长度为 10。


let urls = Array(10).fill('https://juejin.im/post/5c889a2b5188257edb45e603?utm_source=wechat')

let callback = function (resolve) {
    console.log(resolve);
    console.log(`ready`);
}

let max = 5

sendRequest(urls, max, callback)
复制代码

这是 Chrome Inspector 中 Network 面板展示。

根据 Waterfall 可以看出,当第一组 5 个执行完毕后,第 6 个请求开始时间在第 1 个请求结束时间处。表明已经达到了并发效果。

待完善

  1. 处理请求结果正常和异常的地方可以单提取出来,做成可配置项
  2. callback 函数可以调整为一个数组依次执行

致谢

感谢 张鑫旭的粉丝二群 @odex 的解答

另一种解法

Promise.all 分组版本:记一道控制并发数的前端面试题

async/await:不到50行代码实现一个能对请求并发数做限制的通用RequestDecorator