接上一篇文章 深入理解Webpack核心模块WTApable钩子(同步版)
tapable中三个注册方法
- 1 tap(同步) 2 tapAsync(cb) 3 tapPromise(注册的是Promise)
tapable中对三个触发方法
- 1 call 2 callAsync 3 promise
这一章节 我们将分别实现异步的Async版本和Promise版本
异步钩子
- AsyncParallelHook
- AsyncParallelHook的Promise版本
- AsyncSeriesHook
- AsyncSeriesHook的Promise版本
- AsyncSeriesWaterfallHook
- AsyncSeriesWaterfallHook的Promise版本
异步的钩子分为并行和串行的钩子,并行是指 等待所有并发的异步事件执行之后再执行最终的异步回调。
而串行是值 第一步执行完毕再去执行第二步,以此类推,直到执行完所有回调再去执行最终的异步回调。
AsyncParallelHook
AsyncParallelHook是异步并行的钩子,上代码:
const { AsyncParallelHook } = require('tapable');
class Hook{
constructor(){
this.hooks = new AsyncParallelHook(['name']);
}
tap(){
/** 异步的注册方法是tapAsync()
* 并且有回调函数cb.
*/
this.hooks.tapAsync('node',function(name,cb){
setTimeout(()=>{
console.log('node',name);
cb();
},1000);
});
this.hooks.tapAsync('react',function(name,cb){
setTimeout(()=>{
console.log('react',name);
cb();
},1000);
});
}
start(){
/** 异步的触发方法是callAsync()
* 多了一个最终的回调函数 fn.
*/
this.hooks.callAsync('call end.',function(){
console.log('最终的回调');
});
}
}
let h = new Hook();
h.tap();/** 类似订阅 */
h.start();/** 类似发布 */
/* 打印顺序:
node call end.
react call end.
最终的回调
*/
等待1s后,分别执行了node call end和react callend 最后执行了最终的回调fn.
手动实现:
class AsyncParallelHook{
constructor(args){ /* args -> ['name']) */
this.tasks = [];
}
/** tap接收两个参数 name和fn */
tap(name,fn){
/** 订阅:将fn放入到this.tasks中 */
this.tasks.push(fn);
}
start(...args){
let index = 0;
/** 通过pop()获取到最后一个参数
* finalCallBack() 最终的回调
*/
let finalCallBack = args.pop();
/** 箭头函数绑定this */
let done = () => {
/** 执行done() 每次index+1 */
index++;
if(index === this.tasks.length){
/** 执行最终的回调 */
finalCallBack();
}
}
this.tasks.forEach((task)=>{
/** 执行每个task,传入我们给定的done回调函数 */
task(...args,done);
});
}
}
let h = new AsyncParallelHook(['name']);
/** 订阅 */
h.tap('react',(name,cb)=>{
setTimeout(()=>{
console.log('react',name);
cb();
},1000);
});
h.tap('node',(name,cb)=>{
setTimeout(()=>{
console.log('node',name);
cb();
},1000);
});
/** 发布 */
h.start('end.',function(){
console.log('最终的回调函数');
});
/* 打印顺序:
react end.
node end.
最终的回调函数
*/
AsyncParallelHook的Promise版本
const { AsyncParallelHook } = require('tapable');
class Hook{
constructor(){
this.hooks = new AsyncParallelHook(['name']);
}
tap(){
/** 这里是Promsie写法
* 注册事件的方法为tapPromise
*/
this.hooks.tapPromise('node',function(name){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log('node',name);
resolve();
},1000);
});
});
this.hooks.tapPromise('react',function(name){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log('react',name);
resolve();
},1000);
});
});
}
start(){
/**
* promsie最终返回一个prosise 成功resolve时
* .then即为最终回调
*/
this.hooks.promise('call end.').then(function(){
console.log('最终的回调');
});
}
}
let h = new Hook();
h.tap();
h.start();
/* 打印顺序:
node call end.
react call end.
最终的回调
*/
这里钩子还是AsyncParallelHook钩子,只是写法变成了promise的写法,去掉了回调函数cb().变成了成功时去resolve().其实用Promise可以更好解决异步并行的问题,因为Promise的原型方法上有个all()方法,它的作用就是等待所有promise执行完毕后再去执行最终的promise。我们现在去实现它:
class SyncHook{
constructor(args){
this.tasks = [];
}
tapPromise(name,fn){
this.tasks.push(fn);
}
promise(...args){
/** 利用map方法返回一个新数组的特性 */
let tasks = this.tasks.map((task)=>{
/** 每一个task都是一个Promise */
return task(...args);
});
/** Promise.all() 等待所有Promise都执行完毕 */
return Promise.all(tasks);
}
}
let h = new SyncHook(['name']);
/** 订阅 */
h.tapPromise('react',(name)=>{
return new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log('react',name);
resolve();
},1000);
});
});
h.tapPromise('node',(name)=>{
return new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log('node',name);
resolve();
},1000);
});
});
/** 发布 */
h.promise('end.').then(function(){
console.log('最终的回调函数');
});
/* 打印顺序:
react end.
node end.
最终的回调函数
*/
AsyncSeriesHook
AsyncSeriesHook是异步串行的钩子, 串行,我们刚才说了, 它是一步步去执行的,下一步执行依赖上一步执行是否完成,手动实现:
const { AsyncSeriesHook } = require('tapable');
class Hook{
constructor(){
this.hooks = new AsyncSeriesHook(['name']);
}
tap(){
/** 异步的注册方法是tapAsync()
* 并且有回调函数cb.
*/
this.hooks.tapAsync('node',function(name,cb){
setTimeout(()=>{
console.log('node',name);
cb();
},1000);
});
this.hooks.tapAsync('react',function(name,cb){
/** 此回调要等待上一个回调执行完毕后才开始执行 */
setTimeout(()=>{
console.log('react',name);
cb();
},1000);
});
}
start(){
/** 异步的触发方法是callAsync()
* 多了一个最终的回调函数 fn.
*/
this.hooks.callAsync('call end.',function(){
console.log('最终的回调');
});
}
}
let h = new Hook();
h.tap();
h.start();
/* 打印顺序:
node call end.
react call end. -> 1s后打印
最终的回调 -> 1s后打印
*/
AsyncParallelHook和AsyncSeriesHook的区别是AsyncSeriesHook是串行的异步钩子,也就是说它会等待上一步的执行 只有上一步执行完毕了 才会开始执行下一步。而AsyncParallelHook是并行异步 AsyncParallelHook 是同时并发执行。 ok.手动实现 AsyncSeriesHook:
class AsyncParallelHook{
constructor(args){ /* args -> ['name']) */
this.tasks = [];
}
/** tap接收两个参数 name和fn */
tap(name,fn){
/** 订阅:将fn放入到this.tasks中 */
this.tasks.push(fn);
}
start(...args){
let index = 0;
let finalCallBack = args.pop();
/** 递归执行next()方法 直到执行所有task
* 最后执行最终的回调finalCallBack()
*/
let next = () => {
/** 直到执行完所有task后
* 再执行最终的回调 finalCallBack()
*/
if(index === this.tasks.length){
return finalCallBack();
}
/** index++ 执行每一个task 并传入递归函数next
* 执行完每个task后继续递归执行下一个task
* next === cb,next就是每一步的cb回调
*/
this.tasks[index++](...args,next);
}
/** 执行next() */
next();
}
}
let h = new AsyncParallelHook(['name']);
/** 订阅 */
h.tap('react',(name,cb)=>{
setTimeout(()=>{
console.log('react',name);
cb();
},1000);
});
h.tap('node',(name,cb)=>{
setTimeout(()=>{
console.log('node',name);
cb();
},1000);
});
/** 发布 */
h.start('end.',function(){
console.log('最终的回调函数');
});
/* 打印顺序:
react end.
node end. -> 1s后打印
最终的回调函数 -> 1s后打印
*/
AsyncSeriesHook的Promise版本
const { AsyncSeriesHook } = require('tapable');
class Hook{
constructor(){
this.hooks = new AsyncSeriesHook(['name']);
}
tap(){
/** 这里是Promsie写法
* 注册事件的方法为tapPromise
*/
this.hooks.tapPromise('node',function(name){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log('node',name);
resolve();
},1000);
});
});
this.hooks.tapPromise('react',function(name){
/** 等待上一步 执行完毕之后 再执行 */
return new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log('react',name);
resolve();
},1000);
});
});
}
start(){
/**
* promsie最终返回一个prosise 成功resolve时
* .then即为最终回调
*/
this.hooks.promise('call end.').then(function(){
console.log('最终的回调');
});
}
}
let h = new Hook();
h.tap();
h.start();
/* 打印顺序:
node call end.
react call end. -> 1s后打印
最终的回调 -> 1s后打印
*/
手动实现AsyncSeriesHook的Promise版本
class AsyncSeriesHook{
constructor(args){
this.tasks = [];
}
tapPromise(name,fn){
this.tasks.push(fn);
}
promise(...args){
/** 1 解构 拿到第一个first
* first是一个promise
*/
let [first, ...others] = this.tasks;
/** 4 利用reduce方法 累计执行
* 它最终返回的是一个Promsie
*/
return others.reduce((l,n)=>{
/** 1 下一步的执行依赖上一步的then */
return l.then(()=>{
/** 2 下一步执行依赖上一步结果 */
return n(...args);
});
},first(...args));
}
}
let h = new AsyncSeriesHook(['name']);
/** 订阅 */
h.tapPromise('react',(name)=>{
return new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log('react',name);
resolve();
},1000);
});
});
h.tapPromise('node',(name)=>{
return new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log('node',name);
resolve();
},1000);
});
});
/** 发布 */
h.promise('end.').then(function(){
console.log('最终的回调函数');
});
/* 打印顺序:
react end.
node end. -> 1s后打印
最终的回调函数 -> 1s后打印
*/
最后一个AsyncSeriesWaterfallHook:
AsyncSeriesWaterfallHook
AsyncSeriesWaterfallHook 异步的串行的瀑布钩子,首先 它是一个异步串行的钩子,同时 它的下一步依赖上一步的结果返回:
const { AsyncSeriesWaterfallHook } = require('tapable');
class Hook{
constructor(){
this.hooks = new AsyncSeriesWaterfallHook(['name']);
}
tap(){
this.hooks.tapAsync('node',function(name,cb){
setTimeout(()=>{
console.log('node',name);
/** 第一次参数是err, 第二个参数是传递给下一步的参数 */
cb(null,'第一步返回第二步的结果');
},1000);
});
this.hooks.tapAsync('react',function(data,cb){
/** 此回调要等待上一个回调执行完毕后才开始执行
* 并且 data 是上一步return的结果.
*/
setTimeout(()=>{
console.log('react',data);
cb();
},1000);
});
}
start(){
this.hooks.callAsync('call end.',function(){
console.log('最终的回调');
});
}
}
let h = new Hook();
h.tap();
h.start();
/* 打印顺序:
node call end.
react 第一步返回第二步的结果
最终的回调
*/
我们可以看到 第二步依赖了第一步返回的值, 并且它也是串行的钩子,实现它:
class AsyncParallelHook{
constructor(args){ /* args -> ['name']) */
this.tasks = [];
}
/** tap接收两个参数 name和fn */
tap(name,fn){
/** 订阅:将fn放入到this.tasks中 */
this.tasks.push(fn);
}
start(...args){
let index = 0;
/** 1 拿到最后的最终的回调 */
let finalCallBack = args.pop();
let next = (err,data) => {
/** 拿到每个task */
let task = this.tasks[index];
/** 2 如果没传task 或者全部task都执行完毕
* return 直接执行最终的回调finalCallBack()
*/
if(!task) return finalCallBack();
if(index === 0){
/** 3 执行第一个task
* 并传递参数为原始参数args
*/
task(...args, next);
}else{
/** 4 执行处第二个外的每个task
* 并传递的参数 data
* data ->‘传递给下一步的结果’
*/
task(data, next);
}
index++;
}
/** 执行next() */
next();
}
}
let h = new AsyncParallelHook(['name']);
/** 订阅 */
h.tap('react',(name,cb)=>{
setTimeout(()=>{
console.log('react',name);
cb(null,'传递给下一步的结果');
},1000);
});
h.tap('node',(name,cb)=>{
setTimeout(()=>{
console.log('node',name);
cb();
},1000);
});
/** 发布 */
h.start('end.',function(){
console.log('最终的回调函数');
});
/* 打印顺序:
react end.
node 传递给下一步的结果
最终的回调函数
*/
AsyncSeriesWaterfallHook的Promise版本
const { AsyncSeriesWaterfallHook } = require('tapable');
class Hook{
constructor(){
this.hooks = new AsyncSeriesWaterfallHook(['name']);
}
tap(){
this.hooks.tapPromise('node',function(name){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log('node',name);
/** 在resolve中把结果传给下一步 */
resolve('返回给下一步的结果');
},1000);
});
});
this.hooks.tapPromise('react',function(name){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log('react',name);
resolve();
},1000);
});
});
}
start(){
this.hooks.promise('call end.').then(function(){
console.log('最终的回调');
});
}
}
let h = new Hook();
h.tap();
h.start();
/* 打印顺序:
node call end.
react 返回给下一步的结果
最终的回调
*/
用Promsie实现很简单,手动实现它吧:
class AsyncSeriesHook{
constructor(args){
this.tasks = [];
}
tapPromise(name,fn){
this.tasks.push(fn);
}
promise(...args){
/** 1 解构 拿到第一个first
* first是一个promise
*/
let [first, ...others] = this.tasks;
/** 2 利用reduce方法 累计执行
* 它最终返回的是一个Promsie
*/
return others.reduce((l,n)=>{
return l.then((data)=>{
/** 3 将data传给下一个task 即可 */
return n(data);
});
},first(...args));
}
}
let h = new AsyncSeriesHook(['name']);
/** 订阅 */
h.tapPromise('react',(name)=>{
return new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log('react',name);
resolve('promise-传递给下一步的结果');
},1000);
});
});
h.tapPromise('node',(name)=>{
return new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log('node',name);
resolve();
},1000);
});
});
/** 发布 */
h.promise('end.').then(function(){
console.log('最终的回调函数');
});
/* 打印顺序:
react end.
node promise-传递给下一步的结果
最终的回调函数
*/
ok.至此,我们把tapable的钩子全部解析并手动实现完毕。写文章不易,喜欢的话给个赞或者start~
代码在github上:mock-webpack-tapable