Home > Back-end >  What is the Awaited Type in TypeScript
What is the Awaited Type in TypeScript

Time:01-25

In the recent version of TypeScript 4.5, there is a new type called Awaited. It looks like this type is for handling promises. I found this is not well described in the documentation and I did not find any better examples of it anywhere.

Can someone please explain what this type is for and how it works?

Thank you in advance.

CodePudding user response:

If you're looking for more detailed and explanatory documentation for the Awaited<T> utility type, you might want to look at the relevant section of the TypeScript 4.5 release notes and the implementing pull request microsoft/TypeScript#45350.

The goal of Awaited<T> is to describe the (perhaps surprisingly) complicated type operation represented by await in an async function. When you await a value of some type T, you will get a value of type Awaited<T>:

async function foo<T>(x: T) {
    const y = await x;
    // const y: Awaited<T> in TS4.5 and above 
    // (just T in TS4.4 and below)
}

If the type T of the you are awaiting isn't a Promise of any kind, then Awaited<T> is just the same as T:

async function bar(x: string) {
    const y = await x;
    // const y: string
    // Awaited<string> is just string
}

If the type T you are awaiting is a Promise<U> where U isn't a Promise of any kind, then Awaited<T> is the same as U:

async function baz(x: Promise<string>) {
    const y = await x;
    // const y: string
    // Awaited<Promise<string>> is just string
}

Things become more complicated when the type T you are awaiting is a Promise<Promise<V>>; if V isn't a Promise, then Awaited<T> is the same as V:

async function qux(x: Promise<Promise<string>>) {
    const y = await x;
    // const y: string
}

That's because await recursively unwraps any Promises until it hits a non-Promise (or some awful error condition, like a non-promise object with a then() method); you should never get a Promise out of an await:

async function quux(x: Promise<Promise<Promise<Promise<string>>>>) {
    const y = await x;
    // const y: string
}

So that means Awaited<T> also recursively unwraps promises, via the following recursive conditional type:

/**
 * Recursively unwraps the "awaited type" of a type. 
   Non-promise "thenables" should resolve to `never`. 
   This emulates the behavior of `await`.
 */
type Awaited<T> =
    T extends null | undefined ? T : // special case for `null | undefined` 
                                     // when not in `--strictNullChecks` mode
        T extends object & { then(onfulfilled: infer F, ...args: infer _): any } ? 
        // `await` only unwraps object types with a callable `then`. 
        // Non-object types are not unwrapped
            F extends ((value: infer V, ...args: infer _) => any) ? 
            // if the argument to `then` is callable, extracts the first argument
                Awaited<V> : // recursively unwrap the value
                never : // the argument to `then` was not callable
        T; // non-object or non-thenable

It also handles union types; for example, if you have something which is either a string or a Promise<number> and await it, then you will get either a string or a number out:

async function union(x: string | Promise<number>) {
    const y = await x;
    // const y: string | number
}

And so on with recursively wrapped unions:

async function wha(x: number | Promise<string | Promise<number | Promise<Promise<boolean>>>>) {
    const y = await x;
    // const y: string | number | boolean
}

Before TypeScript 4.5 the above awaits mostly worked, but there were some glitches, especially around the behavior of Promise-combination methods like Promise.all():

async function oops(x1: Promise<string | Promise<number>>, x2: Promise<string | Promise<number>>) {
    const y = await Promise.all([x1, x2]);
    // const y: [string | Promise<number>, string | Promise<number>] in TS4.4 and below            
  • Related