AsyncParallelHook 异步并行钩子
这一节来学习异步钩子。异步钩子分为异步并行钩子和异步串行钩子。
场景描述
比如,上一节的例子,假设我们学习时,有时候想同时学习vue和react,或者在开发中,同一时间会发送多个请求,当所有请求都执行成功后,再执行回调,统一干什么事。这时候就需要异步了。
异步也分为两种。
- 异步串行钩子,即第一个回调执行完,再执行第二个,相互之间有依赖关系
- 异步并行钩子,需要等待所有并发的异步事件执行后,再执行回调的方法
并行肯定比串行的性能好,不过有的场景就需要串行,有的场景无所谓。
AsyncParallelHook (使用回调的方式)
异步并行钩子,当所有异步任务都执行成功后,才会执行最终的成功回调。
tapable中的AsyncParallelHook
// 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。
自己实现一个AsyncParallelHook
每个异步任务执行成功回掉时,记录一下成功任务的次数,当次数等于注册数组的长度时,表示所有异步任务执行成功,然后再去执行最终的成功回调。
// 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。
AsyncParallelHook (使用Promise的方式)
其实上面回调方式的AsyncParallelHook ,有点类似Promise.all()。
当所有异步任务执行完之后,再执行最终的回调。实现方式也差不多,有一个count,用来记录执行成功的结果数,当结果数等于注册任务数时,表示所有任务执行成功,然后就可以执行最终的回调了。
tapable中的AsyncParallelHook(Promise形式)
// 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()
自己实现一个AsyncParallelHook(Promise的形式)
// 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');
})
总结
本节学习的AsyncParallelHook,是异步并行钩子。
异步并行钩子,当所有异步任务都执行成功后,才会执行最终的成功回调。
tapable库有三种注册任务的方式
- tap:同步注册
- tapAsync:异步注册(回调的方式)
- tapPromise:异步注册(promise的方式)
对应的也有三种调用方式
- call:同步执行,对应tap
- callAsync:异步并发执行,对应tapAsync
- promise:异步并发执行,对应tapPromise
tapAsync、callAsync
注册
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) })
执行
js// 异步任务执行,使用callAsync方法 hook.callAsync('cheny', () => { // 异步任务都执行完成后的回调 console.log('end'); })
核心代码
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
注册
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) }) })
执行
js// 异步任务执行,使用promise方法 hook.promise('cheny').then(() => { console.log('end'); })
核心代码
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