接上一篇文章 深刻理解Webpack核心模塊WTApable鉤子(同步版)node
tapable中三個註冊方法react
tapable中對三個觸發方法webpack
這一章節 咱們將分別實現異步的Async版本和Promise版本git
異步鉤子
異步的鉤子分爲並行和串行的鉤子,並行是指 等待全部併發的異步事件執行以後再執行最終的異步回調。
而串行是值 第一步執行完畢再去執行第二步,以此類推,直到執行完全部回調再去執行最終的異步回調。github
AsyncParallelHook
AsyncParallelHook是異步並行的鉤子,上代碼:web
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.segmentfault
手動實現:數組
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。咱們如今去實現它: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