tapable據說了好久,終於下定決心繫統學習一下javascript
const { SyncHook, SyncBailHook, SyncWaterfallHook, SyncLoopHook, AsyncParallelHook, AsyncParallelBailHook, AsyncSeriesHook, AsyncSeriesBailHook, AsyncSeriesWaterfallHook } = require("tapable"); 複製代碼
好的,方法一共是上述這麼多,第一眼看過去,懵逼樹下你和我,因此咱們仍是一點點來,一個個的分析、學習和了解前端
先來個使用的例子,例如前端開發者須要掌握哪些技能?java
const {SyncHook}= require('tapable'); const FrontEnd = new SyncHook(); 複製代碼
ok,就是上面這兩句,咱們建立了個FrontEnd前端開發node
FrontEnd.tap('webpack',()=>{ console.log("get webpack") }); FrontEnd.tap('react',()=>{ console.log("get react") }); 複製代碼
ok,上面的tap就是用來綁定事件的,爲前端開發添加了兩個技能react
FrontEnd.learn=()=>{ FrontEnd.call() }; FrontEnd.learn(); 複製代碼
get webpack
get react
複製代碼
能夠看到,經過上面的調用,咱們的前端開發已經學會了react、webpackjquery
前面知道FrontEnd這個羣體,須要學react、webpack,但落到我的角度,究竟哪個開發者掌握這些技能了呢?webpack
const {SyncHook}= require('tapable'); const FrontEnd = new SyncHook(); FrontEnd.tap('webpack',(name)=>{ console.log(name+" get webpack") }); FrontEnd.tap('react',(name)=>{ console.log(name+" get react") }); FrontEnd.start=(name)=>{ FrontEnd.call(name) }; FrontEnd.start('xiaoming'); 複製代碼
修改前面的代碼,添加參數,預期是輸出xxx get reactweb
undefined get webpack
undefined get react
複製代碼
最終結果是undefined,也就是參數沒傳進去redux
這是由於const FrontEnd = new SyncHook();
建立SyncHook的時候沒有約定參數,只要爲其添加參數便可,以下:api
const {SyncHook}= require('tapable'); const FrontEnd = new SyncHook(['name']);// 添加參數約定 FrontEnd.tap('webpack',(name)=>{ console.log(name+" get webpack") }); FrontEnd.tap('react',(name)=>{ console.log(name+" get react") }); FrontEnd.start=(name)=>{ FrontEnd.call(name) }; FrontEnd.start('xiaoming'); 複製代碼
最終輸出:
xiaoming get webpack
xiaoming get react
複製代碼
SyncHook實現比較簡單,就是最簡單的訂閱發佈
class SyncHook { constructor(limit = []){ this.limit= limit; this.tasks = []; } tap(name,task){ this.tasks.push(task); } call(...args){ const param = args.slice(0,this.limit.length); this.tasks.forEach(item=>item(...param)); } } 複製代碼
總結:原理比較簡單,沒有太多技術含量,主要就是一個同步的鉤子函數
熔斷機制,若是前一個事件return true
,則再也不執行下一個,仍是前面的例子:
const {SyncBailHook} =require('tapable'); const FrontEnd = new SyncBailHook(['name']); FrontEnd.tap('webpack',(name)=>{ console.log(name+" get webpack ") }); FrontEnd.tap('react',(name)=>{ console.log(name+" get react") }); FrontEnd.start=(...args)=>{ FrontEnd.call(...args) }; FrontEnd.start('xiaoming'); 複製代碼
此時,把函數從SyncHook換成SyncBailHook,執行的結果沒有任何區別
but,思考一下,學習很容易會學不下去,因此修改一下咱們的例子:
const {SyncBailHook} =require('tapable'); const FrontEnd = new SyncBailHook(['name']); FrontEnd.tap('webpack',(name)=>{ console.log(name+" get webpack ") return '學不動了啊!'; }); FrontEnd.tap('react',(name)=>{ console.log(name+" get react") }); FrontEnd.start=(...args)=>{ FrontEnd.call(...args) }; FrontEnd.start('xiaoming'); 複製代碼
此時僅輸出:
xiaoming get webpack
複製代碼
後面的react沒有執行
總結:
SyncBailHook也十分簡單,仍是以前那個例子:
class SyncBailHook { constructor(limit = []){ this.limit= limit; this.tasks = []; } tap(name,task){ this.tasks.push(task); } call(...args){ const param = args.slice(0,this.limit.length); this.tasks.some(item=>item(...param));// 只改了一行 } } 複製代碼
能夠看到,和上面SyncHook十分類似,無非就是把執行函數forEach,換成some,由於some是阻塞式執行,當返回true,則不會執行後面的內容
仍是先來個使用的例子,例如前端,技能都是一個個學的,要學完webpack再學react,例如:
const {SyncWaterfallHook} = require('tapable'); const FrontEnd = new SyncWaterfallHook(['name']); FrontEnd.tap('webpack',(name)=>{ console.log(name+" get webpack ") return '學完webpack了,該學react了'; }); FrontEnd.tap('react',(name)=>{ console.log(name+" get react") }); FrontEnd.start=(...args)=>{ FrontEnd.call(...args) }; FrontEnd.start('xiaoming'); 複製代碼
此時輸出:
xiaoming get webpack
學完webpack了,該學react了 get react
複製代碼
class SyncWaterfallHook { constructor(limit = []){ this.limit= limit; this.tasks = []; } tap(name,task){ this.tasks.push(task); } call(...args){ const param = args.slice(0,this.limit.length); const [first,...others] = this.tasks; const ret = first(...param); others.reduce((pre,next)=>{ return next(pre); },ret) } } 複製代碼
SyncWaterfallHook實現也比較簡單
總結:SyncWaterfallHook主要仍是用於函數之間對結果存在依賴的場景
仍是前面的例子,若是一次學不懂一門技術,那就要多學幾遍,例如:
const FrontEnd = new SyncLoopHook(['name']); let num = 0; FrontEnd.tap('webpack',(name)=>{ console.log(name+" get webpack ") return ++num === 3?undefined:'再學一次'; }); FrontEnd.tap('react',(name)=>{ console.log(name+" get react") }); FrontEnd.start=(...args)=>{ FrontEnd.call(...args) }; FrontEnd.start('xiaoming'); 複製代碼
上面執行的結果是:
xiaoming get webpack
xiaoming get webpack
xiaoming get webpack
xiaoming get react
複製代碼
總結:主要場景是同一任務,須要執行屢次
class SyncLoopHook { constructor(limit = []){ this.limit= limit; this.tasks = []; } tap(name,task){ this.tasks.push(task); } call(...args){ const param = args.slice(0,this.limit.length); let index = 0; while(index<this.tasks.length){ const result = this.tasks[index](...param); if(result === undefined){ index++; } } } } 複製代碼
也能夠換doWhile來實現
class SyncLoopHook { constructor(limit = []){ this.limit= limit; this.tasks = []; } tap(name,task){ this.tasks.push(task); } call(...args){ const param = args.slice(0,this.limit.length); this.tasks.forEach(task=>{ let ret; do{ ret = task(...param); }while(ret!=undefined) }) } } 複製代碼
總結:SyncLoopHook這個使用場景相對較少,不過了解一下也好
前面瞭解的都是同步hook,更關鍵的是異步hook
舉個例子,同窗小王說去學前端了,但你也不知道他何時學完,只有他學完告訴你,你才知道他學完了,例:
const {AsyncParallelHook} = require('tapable'); const FrontEnd = new AsyncParallelHook(['name']); FrontEnd.tapAsync('webpack',(name,cb)=>{ setTimeout(() => { console.log(name+" get webpack ") cb(); }, 1000); }); FrontEnd.tapAsync('react',(name,cb)=>{ setTimeout(() => { console.log(name+" get react") cb(); }, 1000); }); FrontEnd.start=(...args)=>{ FrontEnd.callAsync(...args,()=>{ console.log("end"); }) }; FrontEnd.start('小王'); 複製代碼
最終輸出:
小王 get webpack
小王 get react
end
複製代碼
class AsyncParallelHook { constructor(limit = []){ this.limit= limit; this.tasks = []; } tapAsync(name,task){ this.tasks.push(task); } callAsync(...args){ const finalCallBack = args.pop(); const param = args.slice(0,this.limit.length); let index = 0; const done=()=>{ index++; if(index === this.tasks.length){ finalCallBack(); } } this.tasks.forEach(item=>item(...param,done)) } } 複製代碼
總結:AsyncParallelHook解決的問題和promise.all相似,都是用於解決異步並行的問題
前面雖然用:AsyncParralleHook可以解決異步,但並無使用primise,也沒有類promise的概念
const {AsyncParallelHook} = require('tapable'); const FrontEnd = new AsyncParallelHook(['name']); FrontEnd.tapPromise('webpack',(name)=>{ return new Promise((resolve)=>{ setTimeout(() => { console.log(name+" get webpack ") resolve(); }, 1000); }) }); FrontEnd.tapPromise('react',(name,cb)=>{ return new Promise((resolve)=>{ setTimeout(() => { console.log(name+" get react ") resolve(); }, 1000); }) }); FrontEnd.start=(...args)=>{ FrontEnd.promise(...args).then(()=>{ console.log("end"); }) }; FrontEnd.start('小王'); 複製代碼
調用上面的api後,輸出:
小王 get webpack
小王 get react
end
複製代碼
總結:
class AsyncParallelHook { constructor(limit = []){ this.limit= limit; this.tasks = []; } tapPromise(name,task){ this.tasks.push(task); } promise(...args){ const param = args.slice(0,this.limit.length); const tasks = this.tasks.map(task=>task(...param)); return Promise.all(tasks) } } 複製代碼
AsyncParallelBailHook這個鉤子和前面的鉤子不太同樣 按前面的例子來說:
這就是AsyncParallelBailHook處理的事情
const {AsyncParallelBailHook} = require('tapable'); const FrontEnd = new AsyncParallelBailHook(['name']); FrontEnd.tapPromise('webpack',(name)=>{ return new Promise((resolve,reject)=>{ setTimeout(() => { console.log(name+" get webpack ") reject('小王學崩了!'); }, 1000); }) }); FrontEnd.tapPromise('react',(name,cb)=>{ return new Promise((resolve)=>{ setTimeout(() => { console.log(name+" get react ") resolve(); }, 2000); }) }); FrontEnd.start=(...args)=>{ FrontEnd.promise(...args).then(()=>{ console.log("end"); },(err)=>{ console.log("據說:",err) }) }; FrontEnd.start('小王'); 複製代碼
上面代碼執行結果是:
小王 get webpack
據說: 小王學崩了!
小王 get react
複製代碼
再看一個例子:
const {AsyncParallelBailHook} = require('tapable'); const FrontEnd = new AsyncParallelBailHook(['name']); FrontEnd.tapPromise('webpack',(name)=>{ return new Promise((resolve,reject)=>{ setTimeout(() => { console.log(name+" get webpack ") reject(); }, 1000); }) }); FrontEnd.tapPromise('react',(name,cb)=>{ return new Promise((resolve)=>{ setTimeout(() => { console.log(name+" get react ") resolve(); }, 2000); }) }); FrontEnd.start=(...args)=>{ FrontEnd.promise(...args).then(()=>{ console.log("end"); },(err)=>{ console.log("據說:",err) }) }; FrontEnd.start('小王'); 複製代碼
和上面就改了1行,就是reject內容爲空,此時輸出:
小王 get webpack
小王 get react
end
複製代碼
總結:
這個AsyncParallelBailHook真真燒腦了好一會
class AsyncParallelBailHook { constructor(limit = []){ this.limit= limit; this.tasks = []; } tapPromise(name,task){ this.tasks.push(task); } promise(...args){ const param = args.slice(0,this.limit.length); const tasks = this.tasks.map(task=>{ return new Promise((resolve,reject)=>{ task(...param).then((data)=>{ resolve(data); },(err)=>{ err? reject(err):resolve(); }); }) }); return Promise.all(tasks) } } 複製代碼
前面講的是異步並行,如今該說異步串行了,例如小王,學完webpack纔去學的react,你也不知道他何時學完,但他學完一個就會告訴你一下,例:
const {AsyncSeriesHook} = require('tapable'); const FrontEnd = new AsyncSeriesHook(['name']); console.time('webpack'); console.time('react'); FrontEnd.tapPromise('webpack',(name,cb)=>{ return new Promise((resolve,reject)=>{ setTimeout(() => { console.log(name+" get webpack ") console.timeEnd('webpack'); resolve(); }, 1000); }) }); FrontEnd.tapPromise('react',(name,cb)=>{ return new Promise((resolve)=>{ setTimeout(() => { console.log(name+" get react ") console.timeEnd('react'); resolve(); }, 1000); }) }); FrontEnd.start=(...args)=>{ FrontEnd.promise(...args).then(()=>{ console.log("end"); }) }; FrontEnd.start('小王'); 複製代碼
上面代碼執行結果:
小王 get webpack webpack: 1010.781ms 小王 get react react: 2016.598ms end 複製代碼
總結:AsyncSeriesHook解決的問題是異步串行,例如node的os.cpus()有限,能夠把任務分批次執行,這樣對性能有保障
class AsyncSeriesHook { constructor(limit = []){ this.limit= limit; this.tasks = []; } tapPromise(name,task){ this.tasks.push(task); } promise(...args){ const param = args.slice(0,this.limit.length); const [first,...others] = this.tasks; return others.reduce((pre,next)=>{ return pre.then(()=>next(...param)) },first(...param)) } } 複製代碼
仍是前面的例子,若是小王學前端,學了webapck就完全放棄了,那後面的react也就不用學了
const {AsyncSeriesBailHook} = require('tapable'); const FrontEnd = new AsyncSeriesBailHook(['name']); console.time('webpack'); console.time('react'); FrontEnd.tapPromise('webpack',(name,cb)=>{ return new Promise((resolve,reject)=>{ setTimeout(() => { console.log(name+" get webpack ") console.timeEnd('webpack'); reject('小王完全放棄了'); }, 1000); }) }); FrontEnd.tapPromise('react',(name,cb)=>{ return new Promise((resolve)=>{ setTimeout(() => { console.log(name+" get react ") console.timeEnd('react'); resolve(); }, 1000); }) }); FrontEnd.start=(...args)=>{ FrontEnd.promise(...args).then(()=>{ console.log("end"); }).catch((err)=>{ console.log("err",err) }) }; FrontEnd.start('小王'); 複製代碼
上面代碼輸出:
小王 get webpack webpack: 1010.518ms err 小王完全放棄了 複製代碼
場景:主要是異步串行,若是某一個任務執行的結果reject或者return,那麼後面的都將再也不執行
class AsyncSeriesBailHook { constructor(limit = []){ this.limit= limit; this.tasks = []; } tapPromise(name,task){ this.tasks.push(task); } promise(...args){ const param = args.slice(0,this.limit.length); const [first,...others] = this.tasks; return new Promise((resolve,reject)=>{ others.reduce((pre,next,index,arr)=>{ return pre .then(()=>next(...param)) .catch((err=>{ arr.splice(index,arr.length-index); reject(err); })).then(()=>{ (index+1 === arr.length) && resolve(); }) },first(...param)) }) } } 複製代碼
AsyncSeriesBailHook實現難度要高不少
SyncWaterFallHook前面已經瞭解過了,就是前一個執行完的結果會傳遞給下一個執行函數,和AsyncSeriesWaterfallHook的區別就是,一個是同步一個是異步
具體來講,例如只有一本教材,小王學完,小張才能學
const FrontEnd = new AsyncSeriesWaterfallHook(['name']); FrontEnd.tapAsync('webpack',(name,cb)=>{ setTimeout(() => { console.log(name+" get webpack ") cb(null,'小李'); }, 1000); }); FrontEnd.tapAsync('webpack',(name,cb)=>{ setTimeout(() => { console.log(name+" get webpack ") cb(null,'小張'); }, 1000); }); FrontEnd.tapAsync('webpack',(name,cb)=>{ setTimeout(() => { console.log(name+" get webpack ") cb(null,'小紅'); }, 1000); }); FrontEnd.start=(...args)=>{ FrontEnd.callAsync(...args,(data)=>{ console.log("全學完了",) }) }; FrontEnd.start('小王'); 複製代碼
上面代碼,最終輸出:
小王 get webpack
小李 get webpack
小張 get webpack
全學完了
複製代碼
總結:這個的用法和SyncWaterFallHook的用法一致
class AsyncSeriesWaterfallHook { constructor(limit = []){ this.limit= limit; this.tasks = []; } tapAsync(name,task){ this.tasks.push(task); } callAsync(...args){ const param = args.slice(0,this.limit.length); const finalCallBack = args.pop(); let index = 0; const next = (err,data)=>{ const task = this.tasks[index]; if(!task)return finalCallBack(); if(index === 0){ task(...param,next) }else{ task(data,next) } index++; } next(); } } 複製代碼
prmise版本的實現以下:
class AsyncSeriesWaterfallHook { constructor(limit = []){ this.limit= limit; this.tasks = []; } tapPromise(name,task){ this.tasks.push(task); } promise(...args){ const param = args.slice(0,this.limit.length); const [first,...others] = this.tasks; return others.reduce((pre,next)=>{ return pre.then((data)=>{ return data?next(data):next(...param); }) },first(...param)) } } 複製代碼