npm install hookified --save
This was built because we constantly wanted hooks and events extended on libraires we are building such as Keyv and Cacheable. This is a simple way to add hooks and events to your classes.
import { Hookified } from 'hookified'; class MyClass extends Hookified { constructor() { super(); } async myMethodEmittingEvent() { this.emit('message', 'Hello World'); //using Emittery } //with hooks you can pass data in and if they are subscribed via onHook they can modify the data async myMethodWithHooks() Promise<any> { let data = { some: 'data' }; // do something await this.hook('before:myMethod2', data); return data; } }
You can even pass in multiple arguments to the hooks:
import { Hookified } from 'hookified'; class MyClass extends Hookified { constructor() { super(); } async myMethodWithHooks() Promise<any> { let data = { some: 'data' }; let data2 = { some: 'data2' }; // do something await this.hook('before:myMethod2', data, data2); return data; } }
<script type="module"> import { Hookified } from 'https://cdn.jsdelivr.net/npm/hookified/dist/browser/index.js'; class MyClass extends Hookified { constructor() { super(); } async myMethodEmittingEvent() { this.emit('message', 'Hello World'); //using Emittery } //with hooks you can pass data in and if they are subscribed via onHook they can modify the data async myMethodWithHooks() Promise<any> { let data = { some: 'data' }; // do something await this.hook('before:myMethod2', data); return data; } } </script>
if you are not using ESM modules, you can use the following:
<script src="https://cdn.jsdelivr.net/npm/hookified/dist/browser/index.global.js"></script> <script> class MyClass extends Hookified { constructor() { super(); } async myMethodEmittingEvent() { this.emit('message', 'Hello World'); //using Emittery } //with hooks you can pass data in and if they are subscribed via onHook they can modify the data async myMethodWithHooks() Promise<any> { let data = { some: 'data' }; // do something await this.hook('before:myMethod2', data); return data; } } </script>
If set to true, errors thrown in hooks will be thrown. If set to false, errors will be only emitted.
import { Hookified } from 'hookified'; class MyClass extends Hookified { constructor() { super({ throwHookErrors: true }); } } const myClass = new MyClass(); console.log(myClass.throwHookErrors); // true. because it is set in super try { myClass.onHook('error-event', async () => { throw new Error('error'); }); await myClass.hook('error-event'); } catch (error) { console.log(error.message); // error } myClass.throwHookErrors = false; console.log(myClass.throwHookErrors); // false
If set, errors thrown in hooks will be logged to the logger. If not set, errors will be only emitted.
import { Hookified } from 'hookified'; import pino from 'pino'; const logger = pino(); // create a logger instance that is compatible with Logger type class MyClass extends Hookified { constructor() { super({ logger }); } async myMethodWithHooks() Promise<any> { let data = { some: 'data' }; // do something await this.hook('before:myMethod2', data); return data; } } const myClass = new MyClass(); myClass.onHook('before:myMethod2', async () => { throw new Error('error'); }); // when you call before:myMethod2 it will log the error to the logger await myClass.hook('before:myMethod2');
Subscribe to a hook event.
import { Hookified } from 'hookified'; class MyClass extends Hookified { constructor() { super(); } async myMethodWithHooks() Promise<any> { let data = { some: 'data' }; // do something await this.hook('before:myMethod2', data); return data; } } const myClass = new MyClass(); myClass.onHook('before:myMethod2', async (data) => { data.some = 'new data'; });
Subscribe to a hook event once.
import { Hookified } from 'hookified'; class MyClass extends Hookified { constructor() { super(); } async myMethodWithHooks() Promise<any> { let data = { some: 'data' }; // do something await this.hook('before:myMethod2', data); return data; } } const myClass = new MyClass(); myClass.onHookOnce('before:myMethod2', async (data) => { data.some = 'new data'; }); myClass.myMethodWithHooks(); console.log(myClass.hooks.length); // 0
Subscribe to a hook event before all other hooks.
import { Hookified } from 'hookified'; class MyClass extends Hookified { constructor() { super(); } async myMethodWithHooks() Promise<any> { let data = { some: 'data' }; // do something await this.hook('before:myMethod2', data); return data; } } const myClass = new MyClass(); myClass.onHook('before:myMethod2', async (data) => { data.some = 'new data'; }); myClass.preHook('before:myMethod2', async (data) => { data.some = 'will run before new data'; });
Subscribe to a hook event before all other hooks. After it is used once it will be removed.
import { Hookified } from 'hookified'; class MyClass extends Hookified { constructor() { super(); } async myMethodWithHooks() Promise<any> { let data = { some: 'data' }; // do something await this.hook('before:myMethod2', data); return data; } } const myClass = new MyClass(); myClass.onHook('before:myMethod2', async (data) => { data.some = 'new data'; }); myClass.preHook('before:myMethod2', async (data) => { data.some = 'will run before new data'; });
Unsubscribe from a hook event.
import { Hookified } from 'hookified'; class MyClass extends Hookified { constructor() { super(); } async myMethodWithHooks() Promise<any> { let data = { some: 'data' }; // do something await this.hook('before:myMethod2', data); return data; } } const myClass = new MyClass(); const handler = async (data) => { data.some = 'new data'; }; myClass.onHook('before:myMethod2', handler); myClass.removeHook('before:myMethod2', handler);
Run a hook event.
import { Hookified } from 'hookified'; class MyClass extends Hookified { constructor() { super(); } async myMethodWithHooks() Promise<any> { let data = { some: 'data' }; // do something await this.hook('before:myMethod2', data); return data; } }
in this example we are passing multiple arguments to the hook:
import { Hookified } from 'hookified'; class MyClass extends Hookified { constructor() { super(); } async myMethodWithHooks() Promise<any> { let data = { some: 'data' }; let data2 = { some: 'data2' }; // do something await this.hook('before:myMethod2', data, data2); return data; } } const myClass = new MyClass(); myClass.onHook('before:myMethod2', async (data, data2) => { data.some = 'new data'; data2.some = 'new data2'; }); await myClass.myMethodWithHooks();
Get all hooks.
import { Hookified } from 'hookified'; class MyClass extends Hookified { constructor() { super(); } async myMethodWithHooks() Promise<any> { let data = { some: 'data' }; // do something await this.hook('before:myMethod2', data); return data; } } const myClass = new MyClass(); myClass.onHook('before:myMethod2', async (data) => { data.some = 'new data'; }); console.log(myClass.hooks);
Get all hooks for an event.
import { Hookified } from 'hookified'; class MyClass extends Hookified { constructor() { super(); } async myMethodWithHooks() Promise<any> { let data = { some: 'data' }; // do something await this.hook('before:myMethod2', data); return data; } } const myClass = new MyClass(); myClass.onHook('before:myMethod2', async (data) => { data.some = 'new data'; }); console.log(myClass.getHooks('before:myMethod2'));
Clear all hooks for an event.
import { Hookified } from 'hookified'; class MyClass extends Hookified { constructor() { super(); } async myMethodWithHooks() Promise<any> { let data = { some: 'data' }; // do something await this.hook('before:myMethod2', data); return data; } } const myClass = new MyClass(); myClass.onHook('before:myMethod2', async (data) => { data.some = 'new data'; }); myClass.clearHooks('before:myMethod2');
Subscribe to an event.
import { Hookified } from 'hookified'; class MyClass extends Hookified { constructor() { super(); } async myMethodEmittingEvent() { this.emit('message', 'Hello World'); } } const myClass = new MyClass(); myClass.on('message', (message) => { console.log(message); });
Unsubscribe from an event.
import { Hookified } from 'hookified'; class MyClass extends Hookified { constructor() { super(); } async myMethodEmittingEvent() { this.emit('message', 'Hello World'); } } const myClass = new MyClass(); myClass.on('message', (message) => { console.log(message); }); myClass.off('message', (message) => { console.log(message); });
Emit an event.
import { Hookified } from 'hookified'; class MyClass extends Hookified { constructor() { super(); } async myMethodEmittingEvent() { this.emit('message', 'Hello World'); } }
Get all listeners for an event.
import { Hookified } from 'hookified'; class MyClass extends Hookified { constructor() { super(); } async myMethodEmittingEvent() { this.emit('message', 'Hello World'); } } const myClass = new MyClass(); myClass.on('message', (message) => { console.log(message); }); console.log(myClass.listeners('message'));
Remove all listeners for an event.
import { Hookified } from 'hookified'; class MyClass extends Hookified { constructor() { super(); } async myMethodEmittingEvent() { this.emit('message', 'Hello World'); } } const myClass = new MyClass(); myClass.on('message', (message) => { console.log(message); }); myClass.removeAllListeners('message');
Set the maximum number of listeners and will truncate if there are already too many.
import { Hookified } from 'hookified'; class MyClass extends Hookified { constructor() { super(); } async myMethodEmittingEvent() { this.emit('message', 'Hello World'); } } const myClass = new MyClass(); myClass.setMaxListeners(1); myClass.on('message', (message) => { console.log(message); }); myClass.on('message', (message) => { console.log(message); }); // this will not be added and console warning console.log(myClass.listenerCount('message')); // 1
Subscribe to an event once.
import { Hookified } from 'hookified'; class MyClass extends Hookified { constructor() { super(); } } const myClass = new MyClass(); myClass.once('message', (message) => { console.log(message); }); myClass.emit('message', 'Hello World'); myClass.emit('message', 'Hello World'); // this will not be called
Prepend a listener to an event. This will be called before any other listeners.
import { Hookified } from 'hookified'; class MyClass extends Hookified { constructor() { super(); } } const myClass = new MyClass(); myClass.prependListener('message', (message) => { console.log(message); });
Prepend a listener to an event once. This will be called before any other listeners.
import { Hookified } from 'hookified'; class MyClass extends Hookified { constructor() { super(); } } const myClass = new MyClass(); myClass.prependOnceListener('message', (message) => { console.log(message); }); myClass.emit('message', 'Hello World');
Get all event names.
import { Hookified } from 'hookified'; class MyClass extends Hookified { constructor() { super(); } } const myClass = new MyClass(); myClass.on('message', (message) => { console.log(message); }); console.log(myClass.eventNames());
Get the count of listeners for an event or all events if evenName not provided.
import { Hookified } from 'hookified'; class MyClass extends Hookified { constructor() { super(); } } const myClass = new MyClass(); myClass.on('message', (message) => { console.log(message); }); console.log(myClass.listenerCount('message')); // 1
Get all listeners for an event or all events if evenName not provided.
import { Hookified } from 'hookified'; class MyClass extends Hookified { constructor() { super(); } } const myClass = new MyClass(); myClass.on('message', (message) => { console.log(message); }); console.log(myClass.rawListeners('message'));
Hookified is written in TypeScript and tests are written in vitest
. To run the tests, use the following command:
To setup the environment and run the tests:
npm i && npm test
To contribute follow the Contributing Guidelines and Code of Conduct.
We are doing very simple benchmarking to see how this compares to other libraries using tinybench
. This is not a full benchmark but just a simple way to see how it performs. Our goal is to be as close or better than the other libraries including native (EventEmitter).
name | summary | ops/sec | time/op | margin | samples |
---|---|---|---|---|---|
Hookified 1.8.0 | 🥇 | 4M | 306ns | ±2.46% | 3M |
Hookable ^5.5.3 | -71% | 1M | 1µs | ±2.93% | 826K |
This shows how close the native EventEmitter
is to hookified
and eventemitter3
. We are using the same test as above but just emitting events. It is not a fair comparison but it is interesting to see how close they are.
name | summary | ops/sec | time/op | margin | samples |
---|---|---|---|---|---|
Hookified 1.8.0 | 🥇 | 10M | 112ns | ±1.13% | 9M |
EventEmitter3 5.0.1 | -1.3% | 10M | 114ns | ±1.84% | 9M |
EventEmitter v22.12.0 | -1.5% | 9M | 114ns | ±1.18% | 9M |
Emittery 1.1.0 | -92% | 785K | 1µs | ±0.45% | 761K |
Note: the EventEmitter
version is Nodejs versioning.