fny
fny

Reputation: 33517

How to unwrap the type of a Promise?

Say I have the following code:

async promiseOne() {
  return 1
} // => Promise<number>

const promisedOne = promiseOne()

typeof promisedOne // => Promised<number>

How would I go about extracting the type of the promise result (in this simplified case a number) as its own type?

Upvotes: 263

Views: 119729

Answers (8)

Evan Kennedy
Evan Kennedy

Reputation: 4175

Update

As of TypeScript 4.5, we can now use the Awaited keyword, as TmTron wrote in their answer.

I encourage you to view the source code of Awaited, which is recursive and has some very interesting edge cases it catches which my solution did not.

Original

An old question, but I'd like to add my 2 cents. Unwrapping a promise using the Promise type is handy, but what I found is that I usually was interested in figuring out the value of a "thenable" object, like what the await operator does.

For this, I wrote an Await<T> helper, which unwraps promises as well as thenable objects:

type Await<T> = T extends {
    then(onfulfilled?: (value: infer U) => unknown): unknown;
} ? U : T;

Use it like so:

const testPromise = async () => 42
type t1 = Await<ReturnType<typeof testPromise>>
// t1 => number

Upvotes: 67

TmTron
TmTron

Reputation: 19371

Update (typescript >= 4.5)

Since typescript 4.5 we can use Awaited: see Typescript Release notes

const promise = new Promise<string>((res, rej) => res('x'))    
type test0 = Awaited<typeof promise>  // string
type test1 = Awaited<Promise<number>> // number

Stackblitz Example

Original

The ts-toolbelt lib has PromiseOf

import {C} from 'ts-toolbelt'

const promise = new Promise<string>((res, rej) => res('x'))    
type test0 = C.PromiseOf<typeof promise>  // string
type test1 = C.PromiseOf<Promise<number>> // number

Upvotes: 21

Gerrit0
Gerrit0

Reputation: 9182

TypeScript 4.5

The Awaited type is now built in to the language, so you don't need to write one yourself.

type T = Awaited<Promise<PromiseLike<number>> // => number

TypeScript 4.1 through 4.4

You can implement this yourself, with several possible definitions. The simplest is:

type Awaited<T> = T extends PromiseLike<infer U> ? U : T
// Awaited<Promise<number>> = number

Note that this type uses PromiseLike rather than Promise. This is important to properly handle user-defined awaitable objects.

This uses a conditional type to check if T looks like a promise, and unwrap it if it does. However, this will improperly handle Promise<Promise<string>>, unwrapping it to Promise<string>. Awaiting a promise can never give a second promise, so a better definition is to recursively unwrap promises.

type Awaited<T> = T extends PromiseLike<infer U> ? Awaited<U> : T
// Awaited<Promise<Promise<number>>> = number

The definition used by TypeScript 4.5 (source) is more complicated than this to cover edge cases that do not apply to most use cases, but it can be dropped in to any TypeScript 4.1+ project.

TypeScript 2.8 through 4.0

Before TypeScript 4.1, the language did not have intentional support for recursive type aliases. The simple version shown above will still work, but a recursive solution requires an additional object type to trick the compiler into thinking that that the type is not recursive, and then pulling the property out that we want with an indexed access type.

type Awaited<T> = T extends PromiseLike<infer U>
  ? { 0: Awaited<U>; 1: U }[U extends PromiseLike<any> ? 0 : 1]
  : T

This is officially not supported, but in practice, completely fine.

TypeScript before 2.8

Before TypeScript 2.8 this is not directly possible. Unwrapping a promise-like type without the conditional types introduced in 2.8 would require that the generic type be available on the object, so that indexed access types can be used to get the value.

If we limit the scope of the type to a single level of promises, and only accept promises, it is technically possible to do this in 2.8 by using declaration merging to add a property to the global Promise<T> interface.

interface Promise<T> {
    __promiseValue: T
}
type Awaited<T extends Promise<any>> = T["__promiseValue"]

type T = Awaited<Promise<string>> // => string

Upvotes: 457

Michael Bosworth
Michael Bosworth

Reputation: 2183

In two steps:

// First, define a type for async functions returning V
type AsyncFunction<V> = (...args: never[]) => PromiseLike<V>

// Second, use the AsyncFunction + infer to define AsyncReturnType
type AsyncReturnType<V> = V extends AsyncFunction<infer U> ? U : V

In your case, you can then do:

let foo: AsyncReturnType<typeof promiseOne>

If you're just using the AsyncReturnType you don't need to export AsyncFunction. The ...args are never because you're ignoring them.

This answer is similar to Evan's, which I don't want to change, but eliminates the extra <ReturnType> part and (to me) is a bit easier to read.

Upvotes: 1

Birchlabs
Birchlabs

Reputation: 8046

We can look at the parameters of the Promise's then function to infer the type.

type PromisedString = Promise<string>;
type AwaitedPromisedString = Parameters<Parameters<PromisedString['then']>[0] & {}>[0];

const promisedString: PromisedString = Promise.resolve('sup');
const x: AwaitedPromisedString = await promisedString;
// assignment succeeds!
const y: string = x;

We can generalize into a utility type:

type Awaited<T extends Promise<unknown>> = Parameters<Parameters<T['then']>[0] & {}>[0];
const promisedString: Promise<string> = Promise.resolve('sup');
const x: Awaited<typeof promisedString> = await promisedString;
const y: string = x;

Let's break this down:

Promise<string>['then'] a function which accepts a callback. Let's get the parameters of that function.
Parameters<Promise<string>['then']>[0] the first parameter of then: its callback. this is optional, so we'll need to narrow the union to truthy types only.
Parameters<Promise<string>['then']>[0] & {} the callback itself. it's a function; let's grab its parameters.
Parameters<Parameters<Promise<string>['then']>[0] & {}>[0] the first parameter of the callback: the value with which the Promise resolves. This is what we were looking for.

Maybe this answer seems redundant in light of the conditional types above, but I'm finding it's useful for resolving Promises inside of JSDoc, where it's impossible (I think) to define new utility types, but is possible to use existing ones:

/** @param {Parameters<Parameters<ReturnType<import('box2d-wasm')>['then']>[0] & {}>[0]} box2D */
export const demo = box2D => {
};

Upvotes: 3

JLarky
JLarky

Reputation: 10241

First of all, TypeScript 4.1 release notes have this snippet:

type Awaited<T> = T extends PromiseLike<infer U> ? Awaited<U> : T;

which allows you to resolve things like const x = (url: string) => fetch(url).then(x => x.json()), but combining answer from Jon Jaques with comment from ErikE and given that you use version older than 4.1 we get:

type Await<T> = T extends PromiseLike<infer U> ? U : T

and example:

const testPromise = async () => 42
type t1 = Await<ReturnType<typeof testPromise>>
// t1 => number

Upvotes: 54

Richie Bendall
Richie Bendall

Reputation: 9172

You can use type-fest if you have access to npm modules:

import {PromiseValue} from 'type-fest';

async promiseOne() {
  return 1
} // => () => Promise<number>

PromiseValue<promiseOne()>
//=> number

Upvotes: 6

josias
josias

Reputation: 1616

Typescript 3.9 planned on implementing this feature. Unfortunately, it has been moved back and it will probably be in the 4.0 version.

The new type will be called something like awaited. It was delayed due to backwards compatibility issues.

A related discussion on GitHub.

Upvotes: 2

Related Questions