Search code examples
typescripttypes

What is the Awaited Type in TypeScript


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.


Solution

  • 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 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 😭
        // const y: [string | number, string | number] in TS4.5 and above 😊
    }
    

    So the Awaited<T> type was introduced in order to more fully handle the effects of Promise manipulation, in the face of some weird edge cases that people had reported as bugs (which you can see in microsoft/TypeScript#45350).

    In practice you probably don't need to deal with it much yourself, but it's helpful that it exists.

    Playground link to code