Skip to content

AsyncSeriesHook 异步串行钩子

本节来学习异步串行钩子。

上一节的异步并行钩子,是所有任务执行完之后,再执行最终的回调。

异步串行钩子是,第一个任务执行完,再执行第二个,依次执行,执行完最后一个后再执行最终的回调。

(有点像express中的源码,执行完一个再执行另外一个)

AsyncSeriesHook(回调形式)

tapable中的AsyncSeriesHook (回调形式)

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

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

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

    // 用来注册的方法
    tap() { // 注册监听函数
        // 使用 tapAsync 注册,回调 的形式
        this.hook.arch.tapAsync('vue', (name, cb) => {
            setTimeout(() => {
                console.log('vue 111', name);
                cb()
            }, 1000)
        })

        this.hook.arch.tapAsync('react', (name, cb) => {
            setTimeout(() => {
                console.log('react 111', name);
                cb()
            }, 1000)
        })

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

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

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

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

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

先执行vue,一秒后,再输出react,然后输出end

image-20220129173646003

自己实现AsyncSeriesHook(回调形式)

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

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

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

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

    // 执行回调
    // 一个一个执行,第一个执行完之后,再执行下一个
    callAsync(...args) {
        // 拿出最终的回调
        let finalCallback = args.pop()

        let index = 0; // 当前执行到第几个任务了

        let next = () => {

            // 出口 index 正好越界的时候,代表所有的任务都执行完毕了
            if (index === this.tasks.length) {
                return finalCallback()
            }

            let task = this.tasks[index] // 取出数组中注册的任务
            // 取出完之后 让index++,便于获取下一个任务
            index++

            task(...args, next) // 这里的回调就传递next,正好一个一个执行,执行完一个之后,执行下一个
            // 非常像 express 的原理了
        }

        // 先执行一次
        next()

    }
}


let hook = new AsyncSeriesHook(['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,然后输出end

image-20220129180630132

AsyncSeriesHook(promise 形式)

tapable中的AsyncSeriesHook (promise 形式)

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

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

class Lesson {
    // new 对象时 构造函数会被执行
    constructor() {
        // new对象的时候 注册一些钩子
        // 一旦new一个新的实例时,这些钩子就被启用了
        this.hook = {
            // 假如有个架构课
            arch: new AsyncSeriesHook(['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()

先执行vue,再执行react,最后输出end

image-20220129205517385

自己实现AsyncSeriesHook(promise形式)

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

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

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

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

    // 执行回调
    // 一个一个执行,第一个执行完之后,再执行下一个
    promise(...args) {
        // 需要把注册的promise串联起来
        let [first, ...others] = this.tasks
        return others.reduce((pre, cur) => {
            return pre.then(() => { // 当前promise执行成功之后,再执行下一个promise
                return cur(...args)
            })
        }, first(...args))

    }
}


let hook = new AsyncSeriesHook(['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-20220129210522920

总结

异步并行钩子,是所有任务执行完之后,再执行最终的回调。

异步串行钩子是,第一个任务执行完,再执行第二个,依次执行,执行完最后一个后再执行最终的回调。

AsyncSeriesHook 异步串联(回调版本)

  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 index = 0; // 当前执行到第几个任务了
    
            let next = () => {
    
                // 出口 index 正好越界的时候,代表所有的任务都执行完毕了
                if (index === this.tasks.length) {
                    return finalCallback()
                }
    
                let task = this.tasks[index] // 取出数组中注册的任务
                // 取出完之后 让index++,便于获取下一个任务
                index++
    
                task(...args, next) // 这里的回调就传递next,正好一个一个执行,执行完一个之后,执行下一个
                // 非常像 express 的原理了
            }
    
            // 先执行一次
            next()
    
        }

AsyncSeriesHook 异步串联(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 [first, ...others] = this.tasks
        return others.reduce((pre, cur) => {
            return pre.then(() => { // 当前promise执行成功之后,再执行下一个promise
                return cur(...args)
            })
        }, first(...args))
    }

参考

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