How to unpack the return type of a Promise in TypeScript
While making @jpwilliams/distributed-promise I came across an interesting requirement: I needed to acquire the return type of an
async function after it had been resolved. I needed to unwrap a promise.
For example, an
async function that simply returns
"foo" would have the return type
Promise<string>. For my purposes, I needed just the
It turns out that this is very possible since TypeScript 2.8 and the introduction of the
infer keyword back in March 2018. For the requirements I had above, I ended up with the following
type AsyncReturnType<T extends (...args: any) => any> = T extends (...args: any) => Promise<infer U> ? U : T extends (...args: any) => infer U ? U : any
This could be used like so:
type T0 = AsyncReturnType<() => Promise<string>> // string
This type, given a function type, will return either the type of the resolved promise if a promise is returned or the straight return type of the function. This is really cool, but it confused the hell out of me at first glance, so let's step through.
First, this makes heavy use of
infer, which we can use to extract types from matching signatures. A simple example of this would be making a type that takes
any as input
T and returns the
any. We can do this in exactly the same way we did with the function signatures above:
type Unwrap<T> = T extends (infer U) ? U : T
There, we check if
T matches the pattern
something and use
infer to extract that
something. If it does, return our
something, otherwise just return
T. This brings us two the second major point: notice the
Now we have that under our belt, understanding the original type is a bit easier:
// create a generic type type AsyncReturnType<T extends (...args: any) => any> = // if T matches this signature and returns a Promise, extract // U (the type of the resolved promise) and use that, or... T extends (...args: any) => Promise<infer U> ? U : // if T matches this signature and returns anything else, // extract the return value U and use that, or... T extends (...args: any) => infer U ? U : // if everything goes to hell, return an `any` any
Nice! Though I don't need it for my uses, a good catch-all might be to include the
Promise type itself.
type Unwrap<T> = T extends Promise<infer U> ? U : T extends (...args: any) => Promise<infer U> ? U : T extends (...args: any) => infer U ? U : T
As mentioned above, this is used within @jpwilliams/distributed-promise, a small library that can be used to distribute promises across the network and multiple processes.