I have a function that takes as parameters a method name as a string and the object that contains that method. I can easily check that the class of the object contains the method name. But I also want to make sure that it is callable and the return type is of a certain type. How can I do that?
type expected = {
a: string;
}
function handle<T>(method: keyof T, handler : T ) {
const f = handler[method];
return f(); // this does not compile
}
class Handler {
foo() {
return 'bar';
}
baz() {
return { a : 'bar'}
}
}
handle('foo', new Handler()); // compile error - not the expected return type
handle('baz', new Handler()); // should compile
You can solve this by using constrained generic type parameters. Given the type Expected
…
type Expected = { a: string };
…you can create an additional generic type parameter K
for the key (method name) and constrain it by string
. Then constrain T
by "a type that has a property at K
whose value is a 0-arity function that returns the type Expected
". You can annotate this in a concise way using the type utility Record<Keys, Type>
like this: Record<K, () => Expected>
. Finally, annotate the return type of the function using the type Expected
. All together, the description above looks like this:
type Expected = { a: string };
function handle<
K extends PropertyKey,
T extends Record<K, () => Expected>,
>(method: K, handler: T): Expected {
const f = handler[method];
return f();
}
class Handler {
foo() {
return "bar";
}
baz() {
return { a: "bar" };
}
}
handle("foo", new Handler()); /* Error
~~~~~~~~~~~~~
Argument of type 'Handler' is not assignable to parameter of type 'Record<"foo", () => Expected>'.
The types returned by 'foo()' are incompatible between these types.
Type 'string' is not assignable to type 'Expected'.(2345) */
handle("baz", new Handler()); // Ok