The question is in the title. Below an example of what I'm trying to achieve:
const apiResponse = Promise.resolve({data: 1} as {data: any}); // Assume I don't know the type of data
const mapData = <T>(res: {data: T}): T => res.data;
const x = apiResponse.then(mapData<number>); // This one fails to compile
const y = apiResponse.then(mapData); // Succeeded but y is of type any
const z = apiResponse.then(_ => mapData<number>(_)); // Succeeded in some case it is inconvenient to forward parameters
Generics allow creating 'type variables' which can be used to create classes, functions & type aliases that don't need to explicitly define the types that they use. Generics makes it easier to write reusable code.
In C# we can define a generic type argument and instantiate this type at runtime: public class Factory { public T Create<T>() where T: new() { return new T(); } } var factory = new Factory(); var person = factory.
The TypeScript documentation explains Generics as “being able to create a component that can work over a variety of types rather than a single one.” Great! That gives us a basic idea. We are going to use Generics to create some kind of reusable component that can work for a variety of types.
Personally I don't see anything wrong with your "verbose" call
const z = apiResponse.then(_ => mapData<number>(_));
but if you want something else, read on:
Generic function types, where a single function can act as multiple types of function, only let you specify the generic type parameter when you call it. Your mapData
function has the following type:
type GenFunc = <T>(res: { data: T; }) => T
const verifyMapData: GenFunc = mapData; // okay
Note that GenFunc
is not a generic type alias; it is a specific type alias which refers to a generic function type. You can think of the type parameter T
as "belonging to" the call signature and not to the name of the function or the name of any type alias of the function. Since it doesn't belong to the name of the function you can't write mapData<T>
by itself and since it doesn't belong to the GenFunc
type name you can't write GenFunc<T>
either.
Compare this to the distinct but related generic type alias which refers to a family of specific function types:
type GenAlias<T> = (res: {data: T; }) => T
In this type, the generic type parameter "belongs to" the name of the type alias and not to the function type it represents.
Now it is unfortunately true that TypeScript's type system currently lacks the expressiveness necessary to take the GenFunc
type and generate the GenAlias<T>
type automatically. For now at least, you need to write out GenAlias<T>
's type definition yourself by hand, as I just did.
What the compiler can do is recognize that a value of type GenFunc
is assignable to a variable of type GenAlias<T>
for any specific T
you want, so the following compiles just fine:
const mapDataNumber: GenAlias<number> = mapData; // okay
And therefore you can get the following to compile without an extra function call, if it matters:
const w = apiResponse.then(mapDataNumber); // Promise<number>
Furthermore if you don't want to waste a line on a variable assignment you can use a type assertion for a little less type safety but with absolutely no runtime effect:
const v = apiResponse.then(mapData as GenAlias<number>); // Promise<number>
Either one of those should work for you.
Okay, hope that helps. Good luck!
Link to code
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With