In traditional event-base repositories, event is just downstream flow which we can only emit data to listener but can not get any response from listener.
So once i came across from webpack/tapable, i'm sure tapable is the better event-driven architecture then traditional event-emitter like.
The repo is almost a tapable-lite, referring to webpack/tapable.
There are some differences between tapable and hamon:
- hamon do not support
interception,Context,HookMapandMultiHook; - hamon do not support
TapOptions; - hamon haven't implement
SyncLoopHook; - hamon is less performance then tapable, since hamon didn't compile all listeners into a method;
But:
- hamon support self-destroy tapping by returning a
unTapfunction after tapping; - hamon support
exhaustmethod to clean all listeners; - hamon support bucket-distribution by providing a
bucketHashcodefunction when creating hooks;
npm install --save @funnyecho/hamon
import{SyncHook,SyncBailHook,SyncWaterfallHook,AsyncParallelHook,AsyncParallelBailHook,AsyncSeriesHook,AsyncSeriesBailHook,AsyncSeriesWaterfallHook}from"@funnyecho/hamon";The hook listeners executed in different ways depending on the different hook types:
Bail:Allows hook calling exited earily. if listener return with a non-undefined value, and skip remaining downstream listeners. The calling will return with that non-undefined bail value or first element of calling arguments;Waterfall: Allows upstream listener to change the calling arguments of downstream listener by returning a non-undefined value;- Basic: All listeners was called with the same arguments, and returning undefined;
Besides, we can combine with Sync, AsyncParallel, or AsyncSeries for more powerful function:
Sync:tap(listener)to tapping;call(...args)to invoke hook, all listeners were called in synchronous queue;
AsyncParallel:tapAsync(listener)ortapPromise(listener)to tapping;callAsync(...args, cb)orcallPromise(...args)to invoke hook, all listeners were called asynchronously in parallel;- Able to combine with
Bailand basic type;
AsyncSeries:tapAsync(listener)ortapPromise(listener)to tapping;callAsync(...args, cb)orcallPromise(...args)to invoke hook, all listeners were called asynchronously in series;- Able to combine with
Bail,Waterfalland basic type;
Sometimes, we are only intested in a certain clause of hook.
For example, message notification from webscoket was delivered by hook, and our app want to focus on the notification of current chatroom. So we use chatroom id as hashcode. Code demo show below:
import{SyncHook}from'@funnyecho/hamon';// create hook with hashcode calculate functionletmessagHook=newSyncHook((messageEntity)=>messageEntity.chatroomId);letcurrentChatroomId=123;// listen to hook and only focus on currentChatroomIdmessageHook.tap(messageEntity=>{// do something with message received},currentChatroomId);// chatroomId didn't equal to currentChatroomId, listener won't be calledmessageHook.call({chatroomId: 'not current'});// chatroomId was equaled to currentChatroomId, listener was calledmessageHook.call({chatroomId: currentChatroomId});- All types of hook can be created with hashcode calculate function which shall calculate a value from calling arguments.
- Listener can tap to hook with specified hashcode value
- When hook was called with arguments:
- those listeners matched hashcode value were called;
- those listeners did't tap with any hashcode were called;
refer to: docs
import{SyncHook}from'@funnyecho/hamon';// create SyncHooklethook=newSyncHook<[number,string]>();// tapping to SyncHook// calling `unTap` can remove listenerletunTap=hook.tap((a1: number,a2: string)=>{console.log('SyncHook',a1,a2);})// invokehook.call(1,'foo');// console:// SyncHook 1 'foo'import{SyncBailHook}from'@funnyecho/hamon';// create SyncBailHooklethook=newSyncBailHook<[number,string],number>();// tapping to SyncBailHook// calling `unTap` can remove listenerletunTap1=hook.tap((a1: number,a2: string): number=>{console.log('SyncBailHook_1',a1,a2);returna1*a1;})letunTap2=hook.tap((a1: number,a2: string): undefined=>{console.log('SyncBailHook_2',a1,a2);})// invoke// listener1 was called and return `4`, listener2 won't be called// calling return `4`hook.call(2,'foo');// console:// SyncBailHook_1 2 'foo'import{SyncWaterfallHook}from'@funnyecho/hamon';// create SyncHooklethook=newSyncWaterfallHook<[number,string],number>();// tapping to SyncWaterfallHook// calling `unTap` can remove listenerletunTap1=hook.tap((a1: number,a2: string): number=>{console.log('SyncWaterfallHook_1',a1,a2);returna1*a1;})letunTap2=hook.tap((a1: number,a2: string): undefined=>{console.log('SyncWaterfallHook_2',a1,a2);})letunTap3=hook.tap((a1: number,a2: string): number=>{console.log('SyncWaterfallHook_3',a1,a2);returna1+100;})// invoke// listener1 was called with `[2, 'foo']` and return `4`,// listener2 was called with `[4, 'foo']`,// listener3 was called with `[4, 'foo']` and return `104`// calling return `104`hook.call(2,'foo');// console:// SyncBailHook_1 2 'foo'// SyncBailHook_2 4 'foo'// SyncBailHook_3 4 'foo'import{AsyncParallelHook}from'@funnyecho/hamon';lethook=newAsyncParallelHook<[number]>();letunTap1=hook.tapAsync((a1,cb)=>{console.log('AsyncParallelHook_1',a1);setTimeout(()=>{console.log('AsyncParallelHook_1','async',a1);cb();},10);})letunTap2=hook.tapPromise(async(a1)=>{console.log('AsyncParallelHook_2',a1);returnnewPromise(resolve=>{setTimeout(()=>{console.log('AsyncParallelHook_2','promise',a1);resolve();},10);})})// invoke with async callback, calling return undefined/** console output:* AsyncParallelHook_1 2* AsyncParallelHook_2 2* AsyncParallelHook_1 async 2* AsyncParallelHook_2 promsie 2* AsyncParallelHook_async_done*/hook.callAsync(2,()=>{console.log('AsyncParallelHook_async_done');});// invoke with async promise, calling return Promise/** console output:* AsyncParallelHook_1 2* AsyncParallelHook_2 2* AsyncParallelHook_1 async 2* AsyncParallelHook_2 promsie 2* AsyncParallelHook_promise_done*/hook.callPromise(2).then(()=>console.log('AsyncParallelHook_promise_done'));import{AsyncParallelBailHook}from'@funnyecho/hamon';lethook=newAsyncParallelBailHook<[number]>();letunTap1=hook.tapAsync((a1,cb)=>{setTimeout(()=>{// bailedcb(a1*100);},10);})letunTap2=hook.tapPromise(async(a1)=>{returnnewPromise(resolve=>{setTimeout(()=>{resolve();},10);})})// invoke with async callback, calling return undefined/** console output:* AsyncParallelHook_async_done 200*/hook.callAsync(2,(v)=>{console.log('AsyncParallelBailHook_async_done',v);});// invoke with async promise, calling return Promise/** console output:* AsyncParallelBailHook_promise_done 200*/hook.callPromise(2).then((v)=>console.log('AsyncParallelBailHook_promise_done',v));import{AsyncSeriesHook}from'@funnyecho/hamon';lethook=newAsyncSeriesHook<[number]>();letunTap1=hook.tapAsync((a1,cb)=>{console.log('AsyncSeriesHook_1',a1);setTimeout(()=>{console.log('AsyncSeriesHook_1','async',a1);cb();},10);})letunTap2=hook.tapPromise(async(a1)=>{console.log('AsyncSeriesHook_2',a1);returnnewPromise(resolve=>{setTimeout(()=>{console.log('AsyncSeriesHook_2','promise',a1);resolve();},10);})})// invoke with async callback, calling return undefined/** console output:* AsyncSeriesHook_1 2* AsyncSeriesHook_1 async 2* AsyncSeriesHook_2 2* AsyncSeriesHook_2 promsie 2* AsyncSeriesHook_async_done*/hook.callAsync(2,()=>{console.log('AsyncSeriesHook_async_done');});// invoke with async promise, calling return Promise/** console output:* AsyncSeriesHook_1 2* AsyncSeriesHook_1 async 2* AsyncSeriesHook_2 2* AsyncSeriesHook_2 promsie 2* AsyncSeriesHook_async_done*/hook.callPromise(2).then(()=>console.log('AsyncSeriesHook_promise_done'));import{AsyncSeriesBailHook}from'@funnyecho/hamon';lethook=newAsyncSeriesBailHook<[number]>();letunTap1=hook.tapAsync((a1,cb)=>{setTimeout(()=>{// bailedcb(a1*100);},10);})letunTap2=hook.tapPromise(async(a1)=>{returnnewPromise(resolve=>{setTimeout(()=>{resolve();},10);})})// invoke with async callback, calling return undefined/** console output:* AsyncSeriesBailHook_async_done 200*/hook.callAsync(2,(v)=>{console.log('AsyncSeriesBailHook_async_done',v);});// invoke with async promise, calling return Promise/** console output:* AsyncSeriesBailHook_promise_done 200*/hook.callPromise(2).then((v)=>console.log('AsyncSeriesBailHook_promise_done',v));import{AsyncSeriesWaterfallHook}from'@funnyecho/hamon';lethook=newAsyncSeriesWaterfallHook<[number]>();letunTap1=hook.tapAsync((a1,cb)=>{console.log('AsyncSeriesWaterfallHook_1',a1);setTimeout(()=>{// waterfallcb(a1*100);},10);})letunTap2=hook.tapPromise(async(a1)=>{console.log('AsyncSeriesWaterfallHook_2',a1);returnnewPromise(resolve=>{setTimeout(()=>{resolve();},10);})})// invoke with async callback, calling return undefined/** console output:* AsyncSeriesWaterfallHook_1 1* AsyncSeriesWaterfallHook_2 200* AsyncSeriesWaterfallHook_async_done 200*/hook.callAsync(2,(v)=>{console.log('AsyncSeriesWaterfallHook_async_done',v);});// invoke with async promise, calling return Promise/** console output:* AsyncSeriesWaterfallHook_1 1* AsyncSeriesWaterfallHook_2 200* AsyncSeriesWaterfallHook_promise_done 200*/hook.callPromise(2).then((v)=>console.log('AsyncSeriesWaterfallHook_promise_done',v));Hook.prototype.exhaust() can clean all listener of current hook.
Hook.prototype.destroy() do the same thing.