控制并发数量

Mon, July 29, 2024 - 7 min read

关键点:1. 通过执行函数控制并发数量;2. 在单个请求结束后再次调用执行函数;3. 考察对promise的理解

方式1

  • 封装一个timeout模拟request
  • 封装控制并发的类
    • 添加任务函数add
      • 将任务添加到请求队列中(resolve和reject用来标识该任务的状态)
      • 尝试执行
    • 执行任务函数run
      • 判断是否可以执行任务
      • 执行完任务后递归调用
// 模拟发送请求接收到数据:返回一个promise,time时间到了,promise完成
function timeout(time) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve()
        }, time)
    })
}
 
class SuperTask {
    constructor(maxCount = 2) {
        // 最大并发数量
        this.maxCount = maxCount
        // 请求队列
        this.requestQueue = []
        // 当前正在进行请求的并发数
        this.currentCount = 0
    }
    // 添加任务到请求队列中
    add(task) {
        return new Promise((resolve, reject) => {
            this.requestQueue.push({
                task,
                resolve,
                reject
            })
            // 尝试执行任务
            this.run()
        })
    }
    // 执行任务:判断当前的并发数,执行我们的任务
    run() {
        while (this.currentCount < this.maxCount && this.requestQueue.length !== 0) {
            // 拿出任务
            const { task, resolve, reject } = this.requestQueue.shift()
            this.currentCount++
            // 执行任务,在执行完毕后,需要再次调用run
            task().then(resolve, reject).finally(() => {
                // 任务执行完毕
                this.currentCount--
                this.run()
            })
        }
    }
}
 
const superTask = new SuperTask(2)
 
function addTask(time, name) {
    superTask
        .add(() => timeout(time))
        .then(() => {
            console.log(`任务${name}完成`)
        })
}
 
addTask(10000, 1) // 10000s后输出:任务1完成
addTask(5000, 2) // 5000s后输出:任务2完成
addTask(3000, 3) // 8000s后输出:任务3完成
addTask(4000, 4) // 12000s后输出:任务4完成
addTask(5000, 5) // 15000s后输出:任务5完成
addTask(6000, 6) // 18000s后输出:任务6完成

方式2(给一个urls数组)

/**
 * 并发请求
 * @param {string[]} urls 待请求的url数组
 * @param {number} maxNum 最大并发数
 */
function concurRequest(urls, maxNum) {
    return new Promise(resolve => {
        // 边缘条件
        if (urls.length === 0) {
            resolve([])
            return
        }
        // 存储结果
        const result = new Array(urls.length).fill(null)
        // 当前需要的请求位置
        let index = 0
        // 请求
        async function request() {
            const i = index
            const url = urls[index]
            index++
            try {
                const res = await fetch(url)
                result[i] = res
            } catch (err) {
                result[i] = err
            } finally {
                // 递归调用下一次请求
                // 判断当前请求队列是否遍历完
                if (index === urls.length) {
                    // 收集结果
                    resolve(result)
                    return
                }
                request()
            }
        }
  
        const min = Math.min(maxNum, urls.length)
        for (let i = 0; i < min; i++) {
            request()
        }
    })
 
}
 
const urls = []
for (let i = 1; i <= 100; i++) {
    urls.push(`https://jsonplaceholder.typicode.com/todos/${i}`)
}
 
concurRequest(urls, 100).then((res) => {
    console.log(res);
})

面试题:浏览器的最大并发请求数量是多少?为什么这么设计?

在HTTP版本为1.1时,现代浏览器例如Chrome一般对同一域名的最大并发请求数量限制在6个,也就是对同一域开启的TCP链接最多为6个。这种设计主要是为了避免对服务器造成过大的负载,确保网络资源合理使用,并提高用户的网页加载速度和整体浏览体验。

如果并发请求数量过多,可能会导致服务器响应变慢,甚至无法响应,从而影响用户体验。