接上一篇文章 深入理解Webpack核心模块WTApable钩子(同步版)tapable中三个注册方法1 tap(同步) 2 tapAsync(cb) 3 tapPromise(注册的是Promise)tapable中对三个触发方法1 call 2 callAsync 3 promise这一章节 我们将分别实现异步的Async版本和Promise版本异步钩子AsyncParallelHookAsyncParallelHook的Promise版本AsyncSeriesHookAsyncSeriesHook的Promise版本AsyncSeriesWaterfallHookAsyncSeriesWaterfallHook的Promise版本异步的钩子分为并行和串行的钩子,并行是指 等待所有并发的异步事件执行之后再执行最终的异步回调。而串行是值 第一步执行完毕再去执行第二步,以此类推,直到执行完所有回调再去执行最终的异步回调。AsyncParallelHookAsyncParallelHook是异步并行的钩子,上代码: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. 最终的回调函数*/AsyncSeriesHookAsyncSeriesHook是异步串行的钩子, 串行,我们刚才说了, 它是一步步去执行的,下一步执行依赖上一步执行是否完成,手动实现: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.tasksindex++; } /* 执行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