深入理解Webpack核心模块Tapable钩子[异步版]

50次阅读

共计 9570 个字符,预计需要花费 24 分钟才能阅读完成。

接上一篇文章 深入理解 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

正文完
 0