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 string type.

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:

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 ? and : ternary operators being used. Their use here is exactly the same as in regular ol' JavaScript. Cool, huh?

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.

January 05, 2020