hookable icon indicating copy to clipboard operation
hookable copied to clipboard

TypeScript Examples

Open abarke opened this issue 2 years ago • 7 comments

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

abarke avatar Dec 01 '21 14:12 abarke

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?

abarke avatar Dec 01 '21 14:12 abarke

@pi0 any feedback would be most appreciated 🙂

abarke avatar Dec 07 '21 13:12 abarke

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: image

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...

  1. There is a registered hook
  2. 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.

abarke avatar Dec 07 '21 17:12 abarke

@danielroe Can you please help on this issue? 🙏🏼

pi0 avatar Aug 23 '22 12:08 pi0

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 🙏🙏

kaikaibenkai avatar Aug 11 '23 14:08 kaikaibenkai

It would be really helpful to see typescript examples 👀

MichaelGitArt avatar Feb 09 '24 15:02 MichaelGitArt

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

affinage-digital avatar Mar 12 '24 22:03 affinage-digital