Skip to content

AsyncParallelHook 异步并行钩子

这一节来学习异步钩子。异步钩子分为异步并行钩子和异步串行钩子。

场景描述

比如,上一节的例子,假设我们学习时,有时候想同时学习vue和react,或者在开发中,同一时间会发送多个请求,当所有请求都执行成功后,再执行回调,统一干什么事。这时候就需要异步了。

异步也分为两种。

  1. 异步串行钩子,即第一个回调执行完,再执行第二个,相互之间有依赖关系
  2. 异步并行钩子,需要等待所有并发的异步事件执行后,再执行回调的方法

并行肯定比串行的性能好,不过有的场景就需要串行,有的场景无所谓。

AsyncParallelHook (使用回调的方式)

异步并行钩子,当所有异步任务都执行成功后,才会执行最终的成功回调。

tapable中的AsyncParallelHook

js
// webpack\webpack-tapable\5.start.js

// 异步并行钩子 AsyncParallelHook tapable
// 多个异步任务同时执行,都执行完成后执行回调
/* 
    三种注册任务的方法
    1. tap 注册,同步注册,注册的是同步任务,依次执行
    2. tapAsync 注册,异步注册,注册的是异步任务,会有一个回调,当异步任务执行之后,同时执行下这个回调,标识当前异步任务执行成功
        如果,其中有一个回调未执行,则最终的执行成功的回调永远不会执行
    3. tapPromise 注册,异步注册,注册的是异步任务,通过promise封装的
*/
let { AsyncParallelHook } = require('tapable')

class Lesson {
    // new 对象时 构造函数会被执行
    constructor() {
        // new对象的时候 注册一些钩子
        // 一旦new一个新的实例时,这些钩子就被启用了
        this.hook = {
            // 假如有个架构课
            arch: new AsyncParallelHook(['name']), // 可能会传一个参数,参数是可选的
        }
    }

    // 用来注册的方法
    tap() { // 注册监听函数
        this.hook.arch.tapAsync('vue', (name, cb) => {

            // AsyncParallelHook 异步并行钩子
            // 多个异步任务执行完成后,再执行回调
            setTimeout(() => {
                console.log('vue 111', name);
                // 任务执行之后,执行回调
                // 有这个回调就能标识异步什么时间执行完
                cb()
            }, 1000)
        })

        // 拿到钩子实例,实例上就有一个tapAsync方法
        // tapAsync方法,注册异步事件,有一个callback,当任务执行后,会执行这个回调
        this.hook.arch.tapAsync('react', (name, cb) => {
            setTimeout(() => {
                console.log('react 111', name);
                // 任务执行之后,执行回调
                cb()
            }, 1000)
        })

        // 调用这个方法时,会将上面两个事件注册到数组中
    }

    // 启动钩子的方法
    start() {
        // 执行之前注册的事件
        this.hook.arch.callAsync('cheny', function () {
            // 异步并行任务都执行成功之后的回调
            console.log('end');
        })
        // 异步并行钩子 实例上有一个callAsync 方法,执行注册的异步事件
    }
}

// 测试
let l = new Lesson() // 声明一个实例

// 注册两个事件
l.tap()

// 调用start方法 启动钩子
l.start()

看一下执行结果,注册的两个异步任务都执行了回到cb(),所以最终的成功回调执行,输出了end。

image-20220129153055871

自己实现一个AsyncParallelHook

每个异步任务执行成功回掉时,记录一下成功任务的次数,当次数等于注册数组的长度时,表示所有异步任务执行成功,然后再去执行最终的成功回调。

js
// webpack\webpack-tapable\5.case.js

// 异步任务,注册到一个数组中
// 并行执行异步任务
// 当所有异步任务执行成功后,再去执行最终的成功回调
class AsyncParallelHook { // 异步并行钩子

    constructor(args) { // args -> ['name']
        this.tasks = [] // 保存注册的事件
    }

    // 异步注册事件
    tapAsync(name, task) {
        this.tasks.push(task)
    }

    // 执行回调
    callAsync(...args) {
        // 拿到最终执行成功的回调
        let finalCallback = args.pop()
        let count = 0; // 记录异步任务执行成功的个数

        // 每个任务执行成功后,执行的回调
        let done = () => {
            count++ // 每个异步任务执行成功后,记录一下
            if (count === this.tasks.length) {
                // 当所有异步任务都执行成功了,执行最终的回调
                // 所以,但凡有一个异步任务执行完之后,没有调用自己的成功回调,最终的成功回调都不会被执行
                finalCallback()
            }
        }

        // 并发执行
        this.tasks.forEach(task => {
            task(...args, done)
        })
    }
}


let hook = new AsyncParallelHook(['name']) // 参数可以传可以不传

// 注册异步事件 tapAsync
hook.tapAsync('vue', function (name, cb) {
    setTimeout(() => {
        console.log('vue 1111', name);
        cb()
    }, 1000)

})
hook.tapAsync('react', function (name, cb) {
    setTimeout(() => {
        console.log('react 1111', name);
        cb()
    }, 1000)
})

// 异步任务执行,使用callAsync方法
hook.callAsync('cheny', () => {
    // 异步任务都执行完成后的回调
    console.log('end');
})

看一下最终的输出结果,vue和react任务执行完成后都调用了成功回到cb(),所以当所有异步任务执行成功后,执行最终的成功回调,输出end。

image-20220129155003479

AsyncParallelHook (使用Promise的方式)

其实上面回调方式的AsyncParallelHook ,有点类似Promise.all()。

当所有异步任务执行完之后,再执行最终的回调。实现方式也差不多,有一个count,用来记录执行成功的结果数,当结果数等于注册任务数时,表示所有任务执行成功,然后就可以执行最终的回调了。

tapable中的AsyncParallelHook(Promise形式)

js
// webpack\webpack-tapable\6.start.js

// 异步并行钩子 AsyncParallelHook tapable
// 多个异步任务同时执行,都执行完成后执行回调
/* 
    三种注册任务的方法
    1. tap 注册,同步注册,注册的是同步任务,依次执行
    2. tapAsync 注册,异步注册,注册的是异步任务,会有一个回调,当异步任务执行之后,同时执行下这个回调,标识当前异步任务执行成功
        如果,其中有一个回调未执行,则最终的执行成功的回调永远不会执行
    3. tapPromise 注册,异步注册,注册的是异步任务,通过promise封装的
*/
let { AsyncParallelHook } = require('tapable')

class Lesson {
    // new 对象时 构造函数会被执行
    constructor() {
        // new对象的时候 注册一些钩子
        // 一旦new一个新的实例时,这些钩子就被启用了
        this.hook = {
            // 假如有个架构课
            arch: new AsyncParallelHook(['name']), // 可能会传一个参数,参数是可选的
        }
    }

    // 用来注册的方法
    tap() { // 注册监听函数
        // 使用 tapPromise 注册,promise的形式
        this.hook.arch.tapPromise('vue', (name) => {
            return new Promise((resolve, reject) => {
                setTimeout(() => {
                    console.log('vue 111', name);
                    resolve()
                }, 1000)
            })
        })

        this.hook.arch.tapPromise('react', (name) => {
            return new Promise((resolve, reject) => {
                setTimeout(() => {
                    console.log('react 111', name);
                    resolve()
                }, 1000)
            })
        })

        // 调用这个方法时,会将上面两个事件注册到数组中
    }

    // 启动钩子的方法
    start() {
        // 执行之前注册的事件
        this.hook.arch.promise('cheny').then(() => {
            // 异步任务都执行成功之后,执行
            console.log('end');
        })
    }
}

// 测试
let l = new Lesson() // 声明一个实例

// 注册两个事件
l.tap()

// 调用start方法 启动钩子
l.start()

image-20220129160220684

自己实现一个AsyncParallelHook(Promise的形式)

js
// webpack\webpack-tapable\6.case.js

// 异步任务,注册到一个数组中
// 并行执行异步任务
// 当所有异步任务执行成功后,再去执行最终的成功回调
class AsyncParallelHook { // 异步并行钩子

    constructor(args) { // args -> ['name']
        this.tasks = [] // 保存注册的事件
    }

    // 异步注册事件
    tapPromise(name, task) {
        this.tasks.push(task)
    }

    // 执行回调
    promise(...args) {
        // 把每个任务依次执行,拿到返回结果,每一个结果都是一个promise
        let tasks = this.tasks.map(task => task(...args))

        return Promise.all(tasks) // 都执行成功返回
    }
}


let hook = new AsyncParallelHook(['name']) // 参数可以传可以不传

// 注册异步事件 tapPromise
hook.tapPromise('vue', function (name) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('vue 1111', name);
            resolve()
        }, 1000)
    })


})
hook.tapPromise('react', function (name) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('react 1111', name);
            resolve()
        }, 1000)
    })

})

// 异步任务执行,使用promise方法
hook.promise('cheny').then(() => {
    console.log('end');
})

image-20220129162033309

总结

本节学习的AsyncParallelHook,是异步并行钩子。

异步并行钩子,当所有异步任务都执行成功后,才会执行最终的成功回调。

tapable库有三种注册任务的方式

  1. tap:同步注册
  2. tapAsync:异步注册(回调的方式)
  3. tapPromise:异步注册(promise的方式)

对应的也有三种调用方式

  1. call:同步执行,对应tap
  2. callAsync:异步并发执行,对应tapAsync
  3. promise:异步并发执行,对应tapPromise

tapAsync、callAsync

  1. 注册

    js
    // 注册异步事件 tapAsync
    hook.tapAsync('vue', function (name, cb) {
        setTimeout(() => {
            console.log('vue 1111', name);
            cb()
        }, 1000)
    
    })
    hook.tapAsync('react', function (name, cb) {
        setTimeout(() => {
            console.log('react 1111', name);
            cb()
        }, 1000)
    })
  2. 执行

    js
    // 异步任务执行,使用callAsync方法
    hook.callAsync('cheny', () => {
        // 异步任务都执行完成后的回调
        console.log('end');
    })
  3. 核心代码

    js
        // 执行回调
        callAsync(...args) {
            // 拿到最终执行成功的回调
            let finalCallback = args.pop()
            let count = 0; // 记录异步任务执行成功的个数
    
            // 每个任务执行成功后,执行的回调
            let done = () => {
                count++ // 每个异步任务执行成功后,记录一下
                if (count === this.tasks.length) {
                    // 当所有异步任务都执行成功了,执行最终的回调
                    // 所以,但凡有一个异步任务执行完之后,没有调用自己的成功回调,最终的成功回调都不会被执行
                    finalCallback()
                }
            }
    
            // 并发执行
            this.tasks.forEach(task => {
                task(...args, done)
            })
        }

tapPromise、promise

  1. 注册

    js
    // 注册异步事件 tapPromise
    hook.tapPromise('vue', function (name) {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                console.log('vue 1111', name);
                resolve()
            }, 1000)
        })
    
    
    })
    hook.tapPromise('react', function (name) {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                console.log('react 1111', name);
                resolve()
            }, 1000)
        })
    
    })
  2. 执行

    js
    // 异步任务执行,使用promise方法
    hook.promise('cheny').then(() => {
        console.log('end');
    })
  3. 核心代码

    js
    // 执行回调
    promise(...args) {
        // 把每个任务依次执行,拿到返回结果,每一个结果都是一个promise
        let tasks = this.tasks.map(task => task(...args))
    
        return Promise.all(tasks) // 都执行成功返回
    }

还有一个异步带保险的钩子,AsyncParallelBailHook,差不多,就是有一个执行失败的时候,就不在执行了。

参考

https://www.bilibili.com/video/BV1a4411e7Bz?p=30&spm_id_from=pageDriver