const {
SyncHook,
SyncBailHook,
SyncWaterfallHook,
SyncLoopHook,
AsyncParallelHook,
AsyncParallelBailHook,
AsyncSeriesHook,
AsyncSeriesBailHook,
AsyncSeriesWaterfallHook
} = require("tapable");
SyncHook
按注冊順序同步執行,無法中斷
const SyncHook =require( './lib/SyncHook')
const hooks ={
sync:new SyncHook(['t1','t2'])
}
hooks.sync.tap('sync',(...args)=>{
console.log('sync',...args)
})
hooks.sync.tap('sync',(...args)=>{
console.log('sync1',...args)
})
hooks.sync.tap('sync2',(...args)=>{
console.log('sync2',...args)
})
hooks.sync.call('a','b')
輸出
sync a b
sync1 a b
sync2 a b
SyncBailHook
按注冊順序同步執行,當回調函數傳回非undefined值時中斷後續回調操作
hooks.syncBai.tap('syncBai',(...args)=>{
console.log('syncBai',...args)
})
hooks.syncBai.tap('syncBai',(...args)=>{
console.log('syncBai1',...args)
return 'syncBai1'
})
hooks.syncBai.tap('syncBai2',(...args)=>{
console.log('syncBai2',...args)
return 3
})
hooks.syncBai.call('a','b')
hooks.syncBai.callAsync('a','b',()=>{
console.log('syncBai.callAsync')
})
輸出
syncBai a b
syncBai1 a b
syncBai a b
syncBai1 a b
syncBai.callAsync
SyncWaterfallHook
按注冊順序同步執行,回調函數傳回值為下一個回調函數的入參
hooks.syncWaterfall.tap('syncWaterfall',(...args)=>{
console.log('syncWaterfall:',...args)
return 'syncWaterfall'
})
hooks.syncWaterfall.tap('syncWaterfall',(...args)=>{
console.log('syncWaterfall1:',...args)
return 'syncWaterfall1'
})
hooks.syncWaterfall.tap('syncWaterfall2',(...args)=>{
console.log('syncWaterfall2:',...args)
})
hooks.syncWaterfall.call('a','b')
hooks.syncWaterfall.callAsync('a','b',()=>{
console.log('syncWaterfall.callAsync')
})
結果
syncWaterfall: a b
syncWaterfall1: syncWaterfall b
syncWaterfall2: syncWaterfall1 b
syncWaterfall: a b
syncWaterfall1: syncWaterfall b
syncWaterfall2: syncWaterfall1 b
syncWaterfall.callAsync
SyncLoopHook
按注冊順序同步執行,回調函數傳回undefined時中斷隊列循環,進入下一個回調函數開始隊列循環,(重點:一定要中斷後才能進入下一個回調,且下一回調執行完後,如果未循環,則從第一個注冊函數開始再一次順序執行)
hooks.syncLoop.tap('syncLoop',(...args)=>{
console.log('syncLoop:',...args)
return ++counter.a < 2 ? 'syncLoop':undefined
})
hooks.syncLoop.tap('syncLoop',(...args)=>{
console.log('syncLoop1:',...args)
return ++counter.b < 3 ? 'syncLoop1':undefined
})
hooks.syncLoop.tap('syncLoop2',(...args)=>{
console.log('syncLoop2:',...args)
return ++counter.c < 4 ? 'syncLoop1':undefined
})
hooks.syncLoop.call('a','b')
hooks.syncLoop.callAsync('a','b',()=>{
console.log('syncLoop.callAsync')
})
結果
syncLoop: a b
syncLoop: a b
syncLoop1: a b
syncLoop: a b
syncLoop1: a b
syncLoop: a b
syncLoop1: a b
syncLoop2: a b
syncLoop: a b
syncLoop1: a b
syncLoop2: a b
syncLoop: a b
syncLoop1: a b
syncLoop2: a b
syncLoop: a b
syncLoop1: a b
syncLoop2: a b
syncLoop: a b
syncLoop1: a b
syncLoop2: a b
syncLoop.callAsync
為了友善後面異步測試,做了幾個工具函數
const time = Date.now()
const log=(...args)=>{
console.log(Date.now()-time,...args)
}
const rejectKey = Symbol('reject')
const handlerFactory = (tap)=>{
return (value,time=0)=>{
let tapHandler = (...args)=>{
log(value+'.'+tap,...args)
return value && value.indexOf('return')> -1 ? (value+'.'+tap):undefined
}
let tapAsyncHandler =(...args)=>{
const cb= args.pop()
tapHandler(...args);
setTimeout(()=>{
tapHandler(...['async.end',...args]);
cb(value?value+'.'+tap:undefined)
},time)
return value && value.indexOf('return')> -1 ? (value+'.'+tap):undefined
}
let tapPromiseHandler =(...args)=>{
return new Promise((resolve,reject)=>{
let handler = resolve
if(args[0] === rejectKey){
args.shift()
handler = reject
}
args.push(handler)
tapAsyncHandler(...args)
})
}
if(tap==='tap') return tapHandler
if(tap==='tapAsync') return tapAsyncHandler
if(tap==='tapPromise') return tapPromiseHandler
}
}
const factory=(tap,hook)=>{
const handler = handlerFactory(tap)
if(tap==='tap') return (...args)=> hook.tap('tap',handler(...args))
if(tap==='tapAsync') return (...args)=> hook.tapAsync('tap',handler(...args))
if(tap==='tapPromise') {
const tapPromise =(...args)=> hook.tapPromise('tap',handler(...args))
tapPromise.reject = (...args)=> hook.tapPromise('tap',handler(...args).bind(null,rejectKey))
return tapPromise
}
}
AsyncParallelHook
按注冊順序同步執行事件函數,若沒有tapPromise或tapAsync異步注冊時,callAsync回調同步執行,反之,當回調函數異步執行完成:
1、成功狀态傳回值不為undefined時(
tapAsync 中callback(result)
、
tapPromise中resolve(result)
,則執行callAsync回調,并将傳回值作為callAsync的參數值
2、失敗狀态返任何值(tapPromise中
resolve(result)
),則執行callAsync回調,并将傳回值作為callAsync的參數值
在這個點上糾結了很久,callAsync回調時機及回調參數值的問題,後來看了源碼發現,callAsync的第一個參數值代表異常資訊(是以有空多看源碼cry…),執行任務中出現任何一個異常時(
reject,callback非空值
)時,則執行callAsync,否則當所有任務執行完成時,調用callAsync
const tap= factory('tap',hooks.asyncParallel)
const tapAsync= factory('tapAsync',hooks.asyncParallel)
const tapPromise= factory('tapPromise',hooks.asyncParallel)
tap('1')
tapAsync('2',2000);
tapAsync('3',5000);
tapPromise('x',3000)
tap('5')
tapAsync('6',5000)
hooks.asyncParallel.callAsync('a','b',(...args)=>{
log('asyncParallel.callAsync',args)
})
結果
2 '1.tap' 'a' 'b'
3 '2.tapAsync' 'a' 'b'
4 '3.tapAsync' 'a' 'b'
5 'x.tapPromise' 'a' 'b'
5 '5.tap' 'a' 'b'
5 '6.tapAsync' 'a' 'b'
2010 '2.tapAsync' 'async.end' 'a' 'b'
2010 'asyncParallel.callAsync' [ '2.tapAsync' ]
3007 'x.tapPromise' 'async.end' 'a' 'b'
5007 '3.tapAsync' 'async.end' 'a' 'b'
5007 '6.tapAsync' 'async.end' 'a' 'b'
若此時将tapAsync(‘2’,2000);改為tapAsync(undefined,2000);
結果
2 '1.tap' 'a' 'b'
3 'undefined.tapAsync' 'a' 'b'
4 '3.tapAsync' 'a' 'b'
4 'x.tapPromise' 'a' 'b'
4 '5.tap' 'a' 'b'
4 '6.tapAsync' 'a' 'b'
2008 'undefined.tapAsync' 'async.end' 'a' 'b'
3009 'x.tapPromise' 'async.end' 'a' 'b'
5006 '3.tapAsync' 'async.end' 'a' 'b'
5007 'asyncParallel.callAsync' [ '3.tapAsync' ]
5009 '6.tapAsync' 'async.end' 'a' 'b'
AsyncParallelBailHook
從名字上看,帶了一個特殊辨別Bai,依此推斷應該與SyncBailHook有類似的功能,而Parallel應該代表與AsyncParallelHook類似
測試1:
tap('return.1') // 帶return 表示回調于傳回列印值
tapPromise.reject('2',3000)
tapAsync('3',2000);
tapAsync('return.5',1000);
tap('2')
tapPromise.reject('x',2000)
tap('5')
tapAsync('6',5000)
tapPromise('7',1000)
hooks.asyncParallelBail.callAsync('a','b',(...args)=>{
log('asyncParallelBail.callAsync',args)
})
列印結果 如下,可看出,當傳回非undfined值是中斷後續事件函數執行,callAsync執行參數值為tap傳回值
1 return.1.tap a b
9 asyncParallelBail.callAsync [ null, 'return.1.tap' ]
測試2:
tap('1')
tapPromise(undefined,3000)
tapPromise('2',3000)
tapPromise.reject(undefined,3000)
tapPromise.reject('reject',3000)
tapAsync('3',2000);
tapAsync(undefined,1000);
tapAsync('return.3',1000);
tap('return.2')
tapPromise.reject('x',2000)
tap('5')
tapAsync('6',5000)
tapPromise('7',1000)
hooks.asyncParallelBail.callAsync('a','b',(...args)=>{
log('asyncParallelBail.callAsync',args)
})
列印結果
1 1.tap a b
8 undefined.tapPromise a b
8 2.tapPromise a b
8 undefined.tapPromise a b
8 reject.tapPromise a b
8 3.tapAsync a b
8 undefined.tapAsync a b
8 return.3.tapAsync a b
8 return.2.tap a b
1011 undefined.tapAsync async.end a b
1012 return.3.tapAsync async.end a b
2009 3.tapAsync async.end a b
3010 undefined.tapPromise async.end a b
3010 2.tapPromise async.end a b
3010 asyncParallelBail.callAsync [ null, '2.tapPromise' ]
3011 undefined.tapPromise async.end a b
3011 reject.tapPromise async.end a b
========== 直接說結論==========
當隻有tap事件傳回非空值時,才會中斷後續所有事件執行,且callAsync參數值為傳回值,其餘不會中斷後續事件執行
callAsync的執行時機在于事件函數最先傳回非空值,注意,此最先不代表事件執行時間的最短,而是完成時目前事件之前所有事件均已完成。
如上述例子:tapAsync(‘return.3’,1000);是最先完成的一批事件,但此時
tapPromise(undefined,3000)
tapPromise('2',3000)
tapPromise.reject(undefined,3000)
tapPromise.reject('reject',3000)
tapAsync('3',2000);
以上任務均還未完成,是以callAsync未執行而且是等tapPromise(‘2’,3000)完成後才執行
asyncSeries
異步串行,即上一個任務完成後,再執行下一個任務,若任務異常(
reject,callback非空值
),則中斷任務
const tap= factory('tap',hooks.asyncSeries)
const tapAsync= factory('tapAsync',hooks.asyncSeries)
const tapPromise= factory('tapPromise',hooks.asyncSeries)
tap('return.1')
tapAsync(undefined,2000);
tapPromise('3000',3000)
tapPromise.reject('3',4000)
tapAsync(undefined,5000);
tapPromise('1000',1000)
tap('5')
tapAsync('6',500)
hooks.asyncSeries.callAsync('a','b',(...args)=>{
log('asyncSeries.callAsync',args)
})
列印結果
2 'return.1.tap' 'a' 'b'
5 'undefined.tapAsync' 'a' 'b'
2009 'undefined.tapAsync' 'async.end' 'a' 'b'
2010 '3000.tapPromise' 'a' 'b'
5013 '3000.tapPromise' 'async.end' 'a' 'b'
5014 '3.tapPromise' 'a' 'b'
9017 '3.tapPromise' 'async.end' 'a' 'b'
9017 'asyncSeries.callAsync' [ 'reject.3.tapPromise' ]
AsyncSeriesBailHook
帶了Bai,按上文了解,此處應該可以用傳回值中斷事件執行
const tap = factory('tap',hooks.asyncSeriesBail)
const tapAsync= factory('tapAsync',hooks.asyncSeriesBail)
const tapPromise= factory('tapPromise',hooks.asyncSeriesBail)
tap('return.1')
tapAsync(undefined,2000);
tapPromise('3000',3000)
tapPromise.reject('3',4000)
列印結果
2 'return.1.tap' 'a' 'b'
4 'asyncSeriesBail.callAsync' [ null, 'return.1.tap' ]
确實傳回非空值中斷後續事件執行,感覺就是asyncSeries+SyncBailHook的元件
但…看看下一個例子
const tap = factory('tap',hooks.asyncSeriesBail)
const tapAsync= factory('tapAsync',hooks.asyncSeriesBail)
const tapPromise= factory('tapPromise',hooks.asyncSeriesBail)
tap('1')
tapAsync(undefined,2000);
tapPromise('3000',3000)
tapPromise.reject('3',4000)
tapAsync(undefined,5000);
tapPromise('1000',1000)
tap('5')
tapAsync('6',500)
hooks.asyncSeriesBail.callAsync('a','b',(...args)=>{
log('asyncSeriesBail.callAsync',args)
})
列印結果
2 '1.tap' 'a' 'b'
5 'undefined.tapAsync' 'a' 'b'
2011 'undefined.tapAsync' 'async.end' 'a' 'b'
2012 '3000.tapPromise' 'a' 'b'
5014 '3000.tapPromise' 'async.end' 'a' 'b'
5014 'asyncSeriesBail.callAsync' [ null, 'resolve.3000.tapPromise' ]
callAsync的執行回調結果
5014 'asyncSeriesBail.callAsync' [ null, 'resolve.3000.tapPromise' ]
上文提及異常是不包含resolve的,但在此函數中,tapPromise也可以通過resolve非空值中斷事件執行
AsyncSeriesWaterfallHook
Series代表事件串行,Waterfall根據上文來為上一事件傳回值做為下一事件輸入值
事件執行異常,中斷後續事件執行,在此函數中,異常情況為(reject、callback非空值),Promise中resolve成功傳回非空值将作為參傳入事件中函數,callback回調函數第二個參數非空時作為參傳入事件中函數
如下:
const tap = factory('tap',hooks.asyncSeriesWaterfall)
const tapAsync= factory('tapAsync',hooks.asyncSeriesWaterfall)
const tapPromise= factory('tapPromise',hooks.asyncSeriesWaterfall)
tap('1')
tapAsync('2',2000);
tapAsync('3',2000);
tapPromise('3000',3000)
// tapPromise.reject('3',4000)
tapAsync(undefined,5000);
tapPromise('1000',1000)
tap('5')
tapAsync('6',500)
hooks.asyncSeriesWaterfall.callAsync('a','b',(...args)=>{
log('asyncSeriesWaterfall.callAsync',args)
})
列印結果
2 'undefined.tap' 'a' 'b'
5 '2.tapAsync' 'a' 'b'
2010 '2.tapAsync' 'async.end' 'a' 'b'
2010 'asyncSeriesWaterfall.callAsync' [ '2.tapAsync' ]
回調值
2.tapAsync
中斷了事件執行
const tap = factory('tap',hooks.asyncSeriesWaterfall)
const tapAsync= factory('tapAsync',hooks.asyncSeriesWaterfall)
const tapPromise= factory('tapPromise',hooks.asyncSeriesWaterfall)
tap('return.1')
tapAsync(undefined,'2',2000);
tapAsync('3',2000);
tapPromise('3000',3000)
// tapPromise.reject('3',4000)
tapAsync(undefined,5000);
tapPromise('1000',1000)
tap('5')
tapAsync('6',500)
hooks.asyncSeriesWaterfall.callAsync('a','b',(...args)=>{
log('asyncSeriesWaterfall.callAsync',args)
})
列印結果
2 'undefined.tap' 'a' 'b'
4 'undefined.tapAsync' 'a' 'b'
2008 'undefined.tapAsync' 'async.end' 'a' 'b'
2008 '3.tapAsync' '2' 'b'
4013 '3.tapAsync' 'async.end' '2' 'b'
4013 'asyncSeriesWaterfall.callAsync' [ '3.tapAsync' ]
2008 ‘3.tapAsync’ ‘2’ ‘b’ 成功接受到上個事件回傳值
以上,為tapable主要的幾個鈎子的使用方式
總結一下,友善記憶
Sync為同步,
Async為異步
Bai代表可中斷事件
Waterfall代表上一事件傳回值可作為下一事件的入參
Series串行事件,上一事件執行完畢後才會執行下一事件