深入理解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.最終的回調函數 */ 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.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
總結
以上是生活随笔為你收集整理的深入理解Webpack核心模块Tapable钩子[异步版]的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: GraphQL 学习
- 下一篇: Spring Boot 单元测试二三事