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串行事件,上一事件执行完毕后才会执行下一事件