Awaited


Problem - On Github

For this problem was want to accept a promise and get the successful results type in return.

type MyAwaited<T> = any

Solution

Looking at the expected outcomes in the test there are a few noteworthy items which the author has put there to narrow the types and what we need to do. The interesting options are:

  1. A simple example - type X = Promise<string>
  2. A recursive example - type Z = Promise<Promise<string | number>>
  3. A thenable example - type T = { then: (onfulfilled: (arg: number) => any) => any }

Lets work through them one by one as they each introduce a new concept.

The basic example

Lets look at the initial basic example of a promise returning a string. We could just return a string, but what we want to do is infer what the return type of the promise is by utilising an extends type check. This way we can automatically figure out the type of the promise so it will work for any type without having to define it.

// If T is a promise, infer its return type and use it
type MyAwaited<T> = T extends Promise<infer U>
  ? U
  : never;

Handling recursion

The recursive case here is a good test because promises have a feature that automatically waits for a returned promise to complete. This means in the real world a Promise returning a Promise will resolve to the inner promises returned value.

We can deal with this through an additional extends check once we have confirmed that T is a Promise. If the inferred value U is a Promise we will recursively call MyAwaited that can then unwrap the value again until we end up without a Promise. Else we can just return U.

// If T is a promise, infer its return type and use it
type MyAwaited<T> = T extends Promise<infer U>
  ? U extends PromiseLike<unknown>
    ? MyAwaited<U>
    : U
  : never;

Thenables

Due to javascript not having Promises until recently(ish) there were many Promise implementations before they were made native. To deal with this JS Promises were made to be compatible with the other implementations. What this means is that any object with a then method will be run as if it is a promise.

Check out the example below to see this in action.


If you have never used it before - codeclip.io is an interactive tool where you can step through the code! Try using the step options below to see outputs.

NOTE - If you just see a blank square above you likely have 3rd party cookies disabled. You can see the same thing here.

Typescript deals with this issue by giving us the type PromiseLike which is an object with a then function. What we will need to do is replace the Promise type with this new type to support any thenable object.

Using this, our type ends up as:

type MyAwaited<T> = T extends PromiseLike<infer U>
    ? U extends PromiseLike<unknown>
        ? MyAwaited<U>
        : U
    : never;

Ensuring a thenable is passed in

The core expectations of the challenge are now met, but there is one little type error left. The challenge creator put in a comment that tells typescript an error should be created // @ts-expect-error - which was placed over the type MyAwaited<number>.

What they are telling us is that our type should require a PromiseLike generic. We currently have no constraints on the generic so lets add one in.

// T should be a PromiseLike to fix our error
type MyAwaited<T extends PromiseLike<any>> =
    T extends PromiseLike<infer U>
    ? U extends PromiseLike<unknown>
        ? MyAwaited<U>
        : U
    : never;


What to read more? Check out more posts below!