Typescript wrapping function with generic type
Solution 1:
The wrapper function is expected to take in a function and return a function of the exact same type signature.
Typescript has no way of knowing how many arguments the passed function takes or what it returns and your function implicitly assumes that it takes 0 arguments (except this) and returns void.
There isn't a nice way to save function signatures in typescript at the moment. However there is a new proposal for later versions that may fix this: https://github.com/Microsoft/TypeScript/issues/5453
For now you can make a generic wrapper that looks something like this.
function wrapper<T extends (...args:any[])=>any>(func: T): T {
return <T>((...args:any[]) => {
console.log('Wrapped Function');
return func(...args);
});
}
With the proposal for Variadic types this function could be written like this
function wrapper<...TArgs,TRet>(func:(...args:...TARGS)=>TRet) {
return (...args:...TARGS) => {
console.log("Wrapped function");
return func(...args);
}
}
Note the main difference here is that there is no cast the above solution had to tell the compiler that the return variable was the same type as the input variable. However with variadic types the arguments themselves can be typed generically. (Note variadic types are not currently in typescript and when the do get included it is fully possible that the above code has syntax errors)
Update for TypeScript 4.3 release: with implemented proposal working code for wrapper will look like
type InferArgs<T> = T extends (...t: [...infer Arg]) => any ? Arg : never;
type InferReturn<T> = T extends (...t: [...infer Arg]) => infer Res ? Res : never;
function getWrapper<TFunc extends (...args: any[]) => any>(func: TFunc)
: (...args: InferArguments<TFunc>) => InferReturn<TFunc> {
return (...args: InferArguments<TFunc>) => {
// something before
try {
return func(...args);
} finally {
// something after;
}
};
}
Some short but helpful info regarding usage of Variadic Tuples with examples can be found here: https://fettblog.eu/variadic-tuple-types-preview/.
Solution 2:
Here's an alternative that preserves the argument and returns types of the inner function without depending on variadic types.
function x(message: string): void {
console.log(`inner ${message}`);
}
export function wrapper<Args extends any[], Return>(
operation: (...operationParameters: Args) => Return,
...parameters: Args
): Return {
console.log(`outer `);
return operation(...parameters);
}
x("abc");
wrapper(x, "xyz");
// output:
//
// inner abc
// outer
// inner xyz
When wrapper
is called with x
, the TS compiler infers its type as function wrapper<[string], void>(operation: (operationParameters_0: string) => void, parameters_0: string): void
.
If you try to call wrapper(x, 123)
, it fails with beautiful type safety: Argument of type '123' is not assignable to parameter of type 'string'.