¿Promesa? ¿Controlador de tareas concurrentes asíncronas?

prefacio

Bienvenido a prestar atención a la cuenta oficial del mismo nombre " Panda of Panda ", el artículo se actualizará sincrónicamente, ¡y también puede unirse rápidamente al grupo de comunicación front-end!

“Implementar una cola de tareas que controle el número de concurrencia, e implementar un controlador de tareas concurrentes asíncronas”, etc., ya son temas muy clásicos escritos a mano, porque involucran asíncrona y concurrencia , entendámoslos brevemente antes de comenzar oficialmente la implementación El concepto, después de todo, solo se puede realizar mejor si sabes por qué, en lugar de simplemente recordar.

494CDD6B.jpg

Asíncrono y Concurrente

asincrónico

JavaScript de un solo subproceso

Todos sabemos que JavaScript tiene un solo subproceso de forma predeterminada , o que JavaScript solo se ejecuta en un subproceso.

[ Nota ] Aunque JavaScript solo se ejecuta en un subproceso, no significa que el motor de JavaScript tenga solo un subproceso. De hecho, el motor de JavaScript tiene varios subprocesos, y un único script solo puede ejecutarse en un subproceso (es decir, el principal) subproceso ). Otros subprocesos cooperan en segundo plano

Y un solo hilo significa que todas las tareas deben ponerse en cola , y la siguiente tarea solo se ejecutará después de que finalice la tarea anterior.Si la tarea anterior lleva mucho tiempo, la última tarea tendrá que esperar.

Generación asíncrona de JavaScript

如果排队是因为计算量大,CPU 处理不过来,这时候也算合理,但很多时候 CPU 是空闲的,是因为 IO 设备(输入/输出设备)很慢(比如 Ajax 操作从网络读取数据),CPU 不得不等着结果返回,才能继续往下执行。

JavaScript 语言的设计者意识到,这时主线程完全可以不管 IO 设备,挂起处于等待中的任务,先运行排在后面的任务,等到 IO 设备返回了结果,再回过头,把挂起的任务继续执行下去。

JavaScript 为了更好的处理异步问题,我们通常都会选择使用 Promiseasync/await

并发

早期计算机的 CPU单核的,一个 CPU同一时间 只能执行 一个进程/线程,当系统中有 多个进程/线程 等待执行时,CPU 只能执行完一个再执行下一个。

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

imagen.png

实现异步并发任务控制器

通过上述内容我们已经知道了 异步并发 的基本概念,现在开始具体实现吧!

题目如下:

假设现在要发送多个请求,但要实现并发控制,即可以通过一个 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()

不同时间的任务:

1.gif

相同时间的任务:

1.gif

方式二:并发控制器 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);
    })
})

最后

欢迎关注同名公众号《熊的猫》,文章会同步更新,也可快速加入前端交流群!

Lo anterior es todo el contenido de este artículo. A través de la optimización gradual finalmente se obtiene un resultado más adecuado. Por supuesto, el método de implementación no es el único mencionado en el artículo. Solo necesita elegir la forma más fácil de entender.

4EA21266.gif

Supongo que te gusta

Origin juejin.im/post/7235574844435431461
Recomendado
Clasificación