npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@funnyecho/hamon

v2.0.2

Published

Event-driven architecture with varying types of hook

Downloads

3

Readme

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 function
let messagHook = new SyncHook((messageEntity) => messageEntity.chatroomId);

let currentChatroomId = 123;

// listen to hook and only focus on currentChatroomId
messageHook.tap(messageEntity => {
  // do something with message received
}, currentChatroomId);

// chatroomId didn't equal to currentChatroomId, listener won't be called
messageHook.call({chatroomId: 'not current'});

// chatroomId was equaled to currentChatroomId, listener was called
messageHook.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 SyncHook
let hook = new SyncHook<[number, string]>();

// tapping to SyncHook
// calling `unTap` can remove listener
let unTap = hook.tap((a1: number, a2: string) => {
  console.log('SyncHook', a1, a2);
})

// invoke
hook.call(1, 'foo');

// console:
// SyncHook 1 'foo'

SyncBailHook

import { SyncBailHook } from '@funnyecho/hamon';

// create SyncBailHook
let hook = new SyncBailHook<[number, string], number>();

// tapping to SyncBailHook
// calling `unTap` can remove listener
let unTap1 = hook.tap((a1: number, a2: string): number => {
  console.log('SyncBailHook_1', a1, a2);
  
  return a1 * a1;
})

let unTap2 = 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 SyncHook
let hook = new SyncWaterfallHook<[number, string], number>();

// tapping to SyncWaterfallHook
// calling `unTap` can remove listener
let unTap1 = hook.tap((a1: number, a2: string): number => {
  console.log('SyncWaterfallHook_1', a1, a2);
  
  return a1 * a1;
})

let unTap2 = hook.tap((a1: number, a2: string): undefined => {
  console.log('SyncWaterfallHook_2', a1, a2);
})

let unTap3 = hook.tap((a1: number, a2: string): number => {
  console.log('SyncWaterfallHook_3', a1, a2);
  
  return a1 + 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';

let hook = new AsyncParallelHook<[number]>();

let unTap1 = hook.tapAsync((a1, cb) => {
  console.log('AsyncParallelHook_1', a1);
  setTimeout(() => {
    console.log('AsyncParallelHook_1', 'async', a1);
    cb();
  }, 10);
})

let unTap2 = hook.tapPromise(async (a1) => {
  console.log('AsyncParallelHook_2', a1);
  return new Promise(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';

let hook = new AsyncParallelBailHook<[number]>();

let unTap1 = hook.tapAsync((a1, cb) => {
  setTimeout(() => {
    // bailed
    cb(a1 * 100);
  }, 10);
})

let unTap2 = hook.tapPromise(async (a1) => {
  return new Promise(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';

let hook = new AsyncSeriesHook<[number]>();

let unTap1 = hook.tapAsync((a1, cb) => {
  console.log('AsyncSeriesHook_1', a1);
  setTimeout(() => {
    console.log('AsyncSeriesHook_1', 'async', a1);
    cb();
  }, 10);
})

let unTap2 = hook.tapPromise(async (a1) => {
  console.log('AsyncSeriesHook_2', a1);
  return new Promise(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';

let hook = new AsyncSeriesBailHook<[number]>();

let unTap1 = hook.tapAsync((a1, cb) => {
  setTimeout(() => {
    // bailed
    cb(a1 * 100);
  }, 10);
})

let unTap2 = hook.tapPromise(async (a1) => {
  return new Promise(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';

let hook = new AsyncSeriesWaterfallHook<[number]>();

let unTap1 = hook.tapAsync((a1, cb) => {
  console.log('AsyncSeriesWaterfallHook_1', a1);
  setTimeout(() => {
    // waterfall
    cb(a1 * 100);
  }, 10);
})

let unTap2 = hook.tapPromise(async (a1) => {
  console.log('AsyncSeriesWaterfallHook_2', a1);
  return new Promise(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.