Home > Mobile >  How to bind a parameter to a generic function
How to bind a parameter to a generic function

Time:12-02

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            
  • Related