深入理解Webpack核心模块Tapable钩子[异步版]
接上一篇文章 深入理解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是异步并行的钩子,上代码:
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:
AsyncSeriesWaterfallHookAsyncSeriesWaterfallHook 异步的串行的瀑布钩子,首先 它是一个异步串行的钩子,同时 它的下一步依赖上一步的结果返回:
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