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.