乐趣区

关于前端:Promise异步并发任务控制器

前言

欢送关注同名公众号《熊的猫》,文章会同步更新,也可疾速退出前端交换群!

“实现一个管制并发数的工作队列、实现一个异步并发工作控制器” 等,曾经是十分经典的手写题目了,因为其中波及 异步 并发 的内容,在正式开始实现之前咱们先来简略理解一下它们的概念,毕竟只有晓得为什么能力更好的实现,而不是单纯的去记忆。

异步 & 并发

异步

单线程的 JavaScript

咱们都晓得 默认状况 JavaScript 单线程 的,又或者说 JavaScript 只在一个线程上运行。

留神 JavaScript 尽管只在一个线程上运行,但不示意 JavaScript 引擎只有一个线程,实际上,JavaScript 引擎有多个线程,单个脚本只能在一个线程上运行(即 主线程),其余线程都是在后盾配合

单线程 就意味着,所有工作须要 排队,前一个工作完结,才会执行后一个工作,如果前一个工作耗时很长,后一个工作就不得不始终等着。

JavaScript 异步的产生

如果排队是因为计算量大,CPU 解决不过去,这时候也算正当,但很多时候 CPU 是闲暇的,是因为 IO 设施(输出 / 输出设备)很慢(比方 Ajax 操作从网络读取数据),CPU 不得不等着后果返回,能力持续往下执行。

JavaScript 语言的设计者意识到,这时主线程齐全能够不论 IO 设施,挂起处于期待中的工作,先运行排在前面的工作,等到 IO 设施返回了后果,再回过头,把挂起的工作继续执行上来。

JavaScript 为了更好的解决异步问题,咱们通常都会抉择应用 Promiseasync/await

并发

晚期计算机的 CPU单核的 ,一个 CPU 同一时间 只能执行 一个过程 / 线程 ,当零碎中有 多个过程 / 线程 期待执行时,CPU 只能执行完一个再执行下一个。

而所谓的 并发 ,指在同一时刻只能有一条 过程指令 执行,但多个 过程指令 被疾速的 交替执行,那么在宏观上看就是多个过程同时执行的成果,但在宏观上并不是同时执行的,只是把工夫分成若干段,使多个过程疾速交替的执行。

实现异步并发工作控制器

通过上述内容咱们曾经晓得了 异步 并发 的基本概念,当初开始具体实现吧!

题目如下:

假如当初要发送多个申请,但要实现并发管制,即能够通过一个 limit 管制并发数,当工作数量超过对应的 limit 限度的并发数时,后续的工作须要提早到 正在执行中 的工作执行完后 再执行 ,并且须要反对动静增加 额定的异步工作 ,同时当 最初一个工作 执行实现,须要执行对应的 callback 函数。

生成工作汇合

// 生成用于测试的工作汇合

const tasks = new Array(10).fill(0).map((v, i) => {return function task() {return new Promise((resolve) => {setTimeout(() => {resolve(i + 1)
            }, i * 1000);
        })
    }
})

形式一:并发管制函数 concurrencyControl

外围思路

  • 通过循环执行以后队列头部的工作
  • 以后队列头部工作执行结束

    • 若是最初一个工作,则执行 callback
    • 否则,继续执行 下一个队头工作
// 并发管制函数
function concurrencyControl(tasks, limit, callback) {const queue = tasks.slice()  // 以后执行的工作队列
    let count = 0 // 已实现的工作数量

    const runTask = () => {while (limit) {
            limit--

            if (queue.length) {const task = queue.shift() // 取出以后队头工作

                task().then(() => {
                    limit++
                    count++

                    if (count === tasks.length) { // 最初一个工作
                        callback() // 执行回调函数}else{runTask() // 继续执行下一个工作
                    }
                })
            }
        }
    }

    return runTask
}

// 测试代码
const sendRequest = concurrencyControl(tasks, 3, (taskId) => {console.log(`task ${taskId} finish!`)
})

sendRequest()

不同工夫的工作:

雷同工夫的工作:

形式二:并发控制器 ConcurrencyControl

形式一 尽管可能简略的实现自动化的并发管制,然而不反对 动静增加工作 的要求,这就意味着要 放弃状态 了,并且如果以后执行的 promise 工作状态为 rejected 时就无奈执行齐全部的工作,因为 task().then 对应的 onreject 的回调没有被提供,上面咱们就能够通过一个 ConcurrencyControl 类来实现。

外围思路

  • 将本来应用到的变量,转换成对应的实例属性
  • 新增 addTask() 办法用于动静增加工作,并且在其外部主动启动工作执行
  • task().then 替换为 task().finally,目标是当对应的 promise 工作为 reject 状态时仍可能执行
class ConcurrencyControl {constructor(tasks, limit, callback) {this.queue = tasks.slice() // 以后执行的工作队列
        this.tasks = tasks // 原始工作汇合
        this.count = 0 // 已实现的工作数量
        this.limit = limit
        this.callback = callback
    }

    runTask() {while (this.limit) {
            this.limit--

            if (this.queue.length) {const task = this.queue.shift() // 取出队头工作

                task().finally(() => {
                    this.limit++
                    this.count++

                    if (this.count === this.tasks.length) { // 最初一个工作
                        this.callback() // 执行回调函数} else {this.runTask() // 继续执行下一个工作
                    }
                })
            }
        }
    }

    addTask(task) {
        // 同步增加工作
        this.queue.push(task)
        this.tasks.push(task)
        // 当间接调用 addTask 也可间接执行
        this.runTask()}
}

// 测试代码
const Control = new ConcurrencyControl(tasks, 3, () => {console.log(`task all finish!`)
})

// 执行队列工作
Control.runTask()

// 增加新工作
Control.addTask(function task() {return new Promise((resolve) => {setTimeout(() => {console.log(`task ${Control.tasks.length} finish!`)
            resolve(Control.tasks.length)
        }, Control.tasks.length * 200);
    })
})

形式三:优化 并发控制器 ConcurrencyControl

外围思路

  • 优化掉 this.count 计数,通过 this.queue.size 来代替
  • 优化掉 this.addTask() 办法中的 this.queue.push(task),通过 this.tasks 的变动来主动影响 this.queue 队列
  • 优化掉 this.limit ++/–,通过 this.queue.size < this.limit 来替换
class ConcurrencyControl {constructor(tasks, limit, callback) {this.tasks = tasks.slice() // 浅拷贝,防止批改原数据
        this.queue = new Set() // 工作队列
        this.limit = limit // 最大并发数
        this.callback = callback // 回调
    }

    runTask() {
        // 边界判断
        if(this.tasks.length == 0) return
        
        // 当工作队列有残余,持续增加工作 
        while (this.queue.size < this.limit) {const task = this.tasks.shift() // 取出队头工作
            
            this.queue.add(task) // 往队列中增加以后执行的工作

            task()
                .finally(() => {this.queue.delete(task) // 当前任务执行结束,从队列中删除改工作

                    if (this.queue.size == 0) {this.callback() // 执行回调函数
                    } else {this.runTask() // 继续执行下一个工作
                    }
                })
        }

    }

    addTask(task) {
        // 同步增加工作
        this.tasks.push(task)
        // 当间接调用 addTask 也可间接执行
        this.runTask()}
}

// 测试代码
const Control = new ConcurrencyControl(tasks, 3, () => {console.log(`task all finish!`)
})

Control.runTask() // 执行队列工作

Control.addTask(function task() { // 增加新工作
    return new Promise((resolve) => {setTimeout(() => {console.log(`task 9999 finish!`)
            resolve(999)
        }, 100);
    })
})

最初

欢送关注同名公众号《熊的猫》,文章会同步更新,也可疾速退出前端交换群!

以上就是本文的全部内容,通过以上三种实现形式的 逐渐优化 最终失去了一个比拟适合的后果,当然实现形式并不是只有文中提到的这种,只须要抉择本人 最容易了解 的形式即可。

退出移动版