Skip to content

Repository files navigation

Hamon

Event-driven architecture in hooking

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, HookMap and MultiHook;
  • 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 unTap function after tapping;
  • hamon support exhaust method to clean all listeners;
  • hamon support bucket-distribution by providing a bucketHashcode function when creating hooks;

Installation

npm install --save @funnyecho/hamon

Hook types

Supported Hook types

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) or tapPromise(listener) to tapping;
    • callAsync(...args, cb) or callPromise(...args) to invoke hook, all listeners were called asynchronously in parallel;
    • Able to combine with Bail and basic type;
  • AsyncSeries
    • tapAsync(listener) or tapPromise(listener) to tapping;
    • callAsync(...args, cb) or callPromise(...args) to invoke hook, all listeners were called asynchronously in series;
    • Able to combine with Bail , Waterfall and basic type;

Hook with hashcode

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;

Documentation

refer to: docs

Usage

Synchook

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'

SyncBailHook

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'

SyncWaterfallHook

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'

AsyncParallelHook

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'));

AsyncParallelBailHook

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));

AsyncSeriesHook

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'));

AsyncSeriesBailHook

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));

AsyncSeriesWaterfallHook

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));

Exhaust Hook Listener

Hook.prototype.exhaust() can clean all listener of current hook.

Hook.prototype.destroy() do the same thing.

About

Event-driven architecture with varying types of hook

Topics

Resources

Stars

14 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages