hookable
hookable copied to clipboard
TypeScript Examples
Would be nice to have examples in the README for using types with Hookable e.g.
import { createHooks } from 'hookable';
export type HookTypes = {
hello: (value: string) => void;
ping: (value: string) => string;
};
// Create a hookable instance
const hooks = createHooks<HookTypes>();
// Hook on 'hello'
hooks.hook('hello', (val: string) => console.log(val));
// Call 'hello' hook and pass 'Hello World!'
hooks.callHook('hello', 'Hello World!');
// Hook on 'ping' that returns 'PONG'
hooks.hook('ping', (val: string) => {
console.log(val);
return 'PONG';
});
// Call 'ping' hook and pass 'PING'
hooks.callHook('ping', 'PING').then((val) => {
console.log(val);
});
However I ran into an unexpected issue with the return type... if I set the type to the following it works without errors. But surely I need to specify the return type here right?
ping: (value: string) => void
Argument of type '(val: string) => string' is not assignable to parameter of type 'never'.(2345)
Editor: https://stackblitz.com/edit/typescript-fkfd5n?devtoolsheight=33&file=index.ts
Just realized that HookCallback
expects a function that returns Promise<void> | void
,
however in the linked example that it seems you can return a value when callHook
resolves.
export type HookCallback = (...args: any) => Promise<void> | void
https://github.com/unjs/hookable/blob/main/src/types.ts#L1
Is that expected?
@pi0 any feedback would be most appreciated 🙂
Also I have noticed that although my JetBrains IDE seems to think that callHook
always returns a Promise, this is simply not the case.
I get this message:
However one would simply go ahead and put a then()
on the end and do something like call a logging method. e.g.
hooks
.callHook(Hook.BEFORE_SERIALIZED, msg)
.then(() => console.log("test"))
This only works if two conditions are met...
- There is a registered hook
- When adding a hook the callBack function is a Promise e.g...
hooks.hook(Hook.BEFORE_SERIALIZED, async (msg) => {
console.log("✅", msg.Message)
})
If any of these conditions are not met then a runtime error occurs (there is no then()
on undefined
).
The type definition
callHook<NameT extends HookNameT>(name: NameT, ...args: Parameters<InferCallback<HooksT, NameT>>): Promise<any>;
should perhaps be
callHook<NameT extends HookNameT>(name: NameT, ...args: Parameters<InferCallback<HooksT, NameT>>): void | Promise<any>;
I edited the type def in Hookable directly in my IDE and the ignored promise warning is gone 🎉
We are building a library that exports the hooks in the config so it's quite normal to have unregistered hooks (noop).
Regarding points 1... The core lib where callHook()
is used cannot be expected to return a Promise, therefore cannot be used with .then()
or await
. Reason: If no hooks are registered then one can not expect a Promise. However this should potentially be the default behavior when callHook()
is called without a registered hook. Simply return a Promise.resolve() by default should this be expected.
Regarding point 2... The hook()
method may be used in another plugin somewhere and should it not use a async method callback then it would break the whole app as I would call .then()
on undefined
.
@danielroe Can you please help on this issue? 🙏🏼
Could anyone answer this In some cases, certain operations require consultation with hooks beforehand.
For example:
const msg = await app.callHook('user:login:before');
if (msg === undefined) {
Message.success('Hi, you are logged in!');
} else {
Message.error('Oops!' + msg);
return;
}
// ...
@danielroe 🙏🙏
It would be really helpful to see typescript examples 👀
Some of working sample:
import { Hookable } from 'hookable';
export class Parser extends Hookable<{
/** super tsdoc comment working too */
someOneHook: (rows: string[]) => void;
}> {
rows: string[] = [];
constructor() {
super(); // init instance of Hookable
}
getRows() {
this.rows = ['a', 'b', 'c'];
this.callHook('someOneHook', this.rows);
}
}
import { Parser } from '...';
const parser = new Parser();
// subscribe
const unregisterSomeOneHook = parser.hook('someOneHook', rows => console.log(rows));
parser.getRows();
// unsubscribe
unregisterSomeOneHook();