天天看點

【tapable】tapable hooks基本使用

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串行事件,上一事件執行完畢後才會執行下一事件