loading...

Advanced TypeScript Exercises - Answer 1

macsikora profile image Maciej Sikora Updated on ・3 min read

The Question I've asked was:

If we have a type which is wrapped type like Promise. How we can get a type which is inside the wrapped type? For example if we have Promise<ExampleType> how to get ExampleType?

The answer

type Transform<A> = A extends Promise<infer Inner> ? Inner : never
type Result = Transform<Promise<string>> // Result is string type

In order to unwrap the promise type we have used infer keyword.

The keyword is helpful with any type constructor, type constructor is a type parameterized by another type variable, so any type which has generic placeholder like A<B>, where A is type constructor parametrized by B.

Examples of using infer

We can use infer also with other types, consider example with Array

type InsideArray<A> = A extends Array<infer Inside> ? Inside : never
type Str = InsideArray<Array<string>>; // Str is string

What about custom parameterized types? Yes will do!

type Surprise<A> = { inside: A }
type UnpackSurprise<S> = S extends Surprise<infer Inside> ? Inside : never
type Num = UnpackSurprise<Surprise<number>> // Num is number

We can even use infer to get mapped types properties

type User = {
    id: number,
    name: string,
}

type Doc = {
    id: string,
}

type GetProperty<T, Prop extends keyof T> = T extends { [K in Prop]: infer Value } ? Value : never

type UserId = GetProperty<User, 'id'>
type DocId = GetProperty<Doc, 'id'>

Question How we can get type of Mapped type property in simpler way? Please write your answer in the comment!

Can we use many type variables and infer them? Sure we can!

type ABC<A, B, C> = { a: A, b: B, c: C }
type ABCIntoTuple<T> 
  = T extends ABC<infer A, infer B, infer C> ? [A, B, C] : never
type Example = ABC<string, boolean, number>
type ExampleTuple = ABCIntoTuple<Example> // [string, boolean, number]

In above example we infer all three type parameters and we put them into 3-n tuple.

Why never?

Type never is a bottom type, it is a type without any value, it is very handy construct for saying that our function doesn't return, or some path of the code is not reachable, more about it you can read in great article from Marius Schulz.

We use never in conditional in order to represent the unhappy path, we are saying that it is a dead end, if you don't pass to our constructor specific type, we just don't have any alternative, our type doesn't work with anything else. Consider how it will behave when we pass to it something which does not match the condition:

type Transform<A> = A extends Promise<infer Inner> ? Inner : never
type OhGosh = Transform<string> // OhGosh evaluates to never

We could be having different representation of the negative path, but never is the best choice, as further type transformation will be useless. We can also set constraint at the argument, and in that way never path will never be reached.

Consider following change:

type Transform<A extends Promise<any>> = A extends Promise<infer Inner> ? Inner : never
type OhGosh = Transform<string> // compilation error

After A extends Promise<any> our utility type Transform is now bullet proof, as compilation will fail for types which do not extend Promise<any>.

Why did I use Promise<any>?

I have put any inside Promise because any is one of unsound types which are also types which can be assigned to everything, it means that every type extend from any, what determines that every Promise kind of type will extend Promise<any>

This series is just starting. If you want to know about new exciting questions from advanced TypeScript please follow me on dev.to and twitter.

Discussion

pic
Editor guide
Collapse
jfet97 profile image
Andrea Simone Costa

I only discovered you, and your series, recently (this morning 🤣) but what a magnificent thing!
It's hard to find such advanced material about TS all around the web. I think I'm going to learn so much!

Now, to answer your question about a simpler GetProperty, what about that?

type GetProperty<T, Prop extends keyof T> = T[Prop]
Enter fullscreen mode Exit fullscreen mode
Collapse
macsikora profile image
Maciej Sikora Author

Yes and we really don't need to wrap it in our type level function. We can just write MyType[MyProp] directly. Thanks for the great comment and kind words!

Collapse
jfet97 profile image
Andrea Simone Costa

You're welcome 😃

Collapse
timon profile image
Timon van Spronsen

Thanks for the excellent explanation! I have been working with TypeScript for quite some time now, but this is the first time I've used infer and now I finally understand it. I think there is definitely some code in our codebase that could be improved by using infer :)

Collapse
macsikora profile image
Maciej Sikora Author

Many thanks for such positive reaction