I have a generic function retrieve
:
function retrieve<T>(endpoint: string, id: string, /* etc */): T {}
I'd like to define a function, e.g retrieveUser
, that would bind the first parameter and also specify T
.
I tried using Function.prototype.bind()
:
const retrieveUser = retrieve.bind(/*this*/ undefined, "/user");
But this prevent me from specifying T
.
I then tried using rest parameters:
type User = { username: string, /* etc */};
const retrieveUser = (...args) => retrieve<User>("/user", ...args);
Which does exactly what I want but Typescript complains:
error TS7019: Rest parameter 'args' implicitly has an 'any[]' type.
error TS2556: A spread argument must either have a tuple type or be passed to a rest parameter.
Is it possible to make Typescript infer args
types from the retrieve
function ?
Is there a better way to achieve my goal ?
CodePudding user response:
TypeScript unfortunately doesn't directly support the kind of higher-order generic function type manipulation you'd like to do here. Ideally there would be a way to say "specify T
in typeof retrieve
with User
", and then you'd just do that before binding the parameter. But there isn't any reasonable way of doing this. Perhaps if the language supported higher-kinded types as requested in microsoft/TypeScript#1213 or generic values as requested in microsoft/TypeScript#17574 there'd be a way to do this cleanly.
The least ad-hoc workaround is to write out the type "specify T
in typeof retrieve
with User
" explicitly, annotate a variable as that type and assign retrieve
to it:
const userRetrieve: (endpoint: string, id: string, /* etc */) => User = retrieve; // okay
The compiler was happy with that, meaning that even though there's no easy way to programmatically specify T
with User
in the type system, it does understand that retrieve
is assignable to the type you'd get if you could. And that means there's type safety here; if you got the type wrong (say (endpoint: number, id: string) => User
) you'd get a compiler error.
Anyway, now you can bind a parameter to userRetrieve
:
const retrieveUser = userRetrieve.bind(undefined, "/user");
// const retrieveUser: (id: string) => User
If the parameters of retrieve
are not generic, you can use the Parameters<T>
utility type to get the list of parameter types and save you some keystrokes:
const userRetrieve: (...args: Parameters<typeof retrieve>) => User = retrieve; // okay
const retrieveUser = userRetrieve.bind(undefined, "/user");
// const retrieveUser: (id: string) => User
And in either case if you don't care as much about type safety as expedience, you can replace the type-annotated variable with a type assertion:
const retrieveUser =
(retrieve as (...args: Parameters<typeof retrieve>) => User).
bind(undefined, "/user");
which is about a short as I can make it.
If the parameters do depend on the generic type parameter, though, Parameters<T>
will lose track of any type parameter:
declare function retrieve<T>(endpoint: string, id: string, somethingGenric: T): T;
const retrieveUser =
(retrieve as (...args: Parameters<typeof retrieve>) => User).
bind(undefined, "/user");
// const retrieveUser: (id: string, somethingGenric: unknown) => User