Open utterances-bot opened 3 years ago
I did this one many times and tried a couple of different variations to it. I believe we could improve this even further, to warn us if we pass something other than a Promise.
With that said, if we were to improve it with that safety implemented, we would not return T
.
Here's my suggestion:
type Awaited<T extends Promise<any>> = T extends Promise<infer R> ? R : never;
So now if we were to do something like:
type Inferred = Awaited<'string'>;
// ^ Type 'string' does not satisfy the constraint 'Promise<any>'.
I learned this from Maciej Sikora, so credits to him!
@dvlden true, by using generic constraints we can narrow the expected type to not allow anything but Promise
.
I was thinking from the different angle, when solving the challenge. I thought, why not to return input type as is if it was not a promise. Like, the result is already there and we don't need to wait for it.
For others, if you want to narrow the input type to be only a Promise
then follow @dvlden solution. If you want to optionally unwrap the promise then don't add a constraint.
The solution originally provided
type Awaited<T> = T extends Promise<infer R> ? R : T;
does not satisfy the following two tests:
Expect<Equal<MyAwaited<Z>, string | number>>,
//recursion
and the one which expects error:
// @ts-expect-error type error = MyAwaited<number>
(Also, the type in solution is misnamed, it has to be MyAwaited, not Awaited)
The correct solution, which satisfies both these tests, as well as deeper recursion levels is the following:
type MyAwaited<P extends Promise<unknown>> = P extends Promise<infer T> ? T extends Promise<unknown> ? MyAwaited<T> : T : P
@pragmasoft-ua seems like they have added more tests to the challenge. Can you elaborate why the T extends Promise<unknown>
check? It seems to me that we could just recursively call the type itself, no?
Can you elaborate why the
T extends Promise<unknown>
check? It seems to me that we could just recursively call the type itself, no?
This exactly happens when we do
T extends Promise<unknown> ? MyAwaited<T>
That is, we unwrap outermost of the nested promises and evaluate our type MyAwaited
recursively. Returning T
in another branch is a recursion termination condition.
In cases when we do not return MyAwaited
there's no recursion. For recursion we need to evaluate the same type again.
I tried a test with triple promise nesting and it worked.
@pragmasoft-ua I meant your T extends Promise<unknown>
. There is no sense in adding one more conditional type to what we already have. Why not T extends Promise<infer R> ? Awaited<R> : T
?
@pragmasoft-ua I meant your
T extends Promise<unknown>
. There is no sense in adding one more conditional type to what we already have. Why notT extends Promise<infer R> ? Awaited<R> : T
?
Agree
@pragmasoft-ua you could do that only if this test didn't exist:
// @ts-expect-error
type error = MyAwaited<number>
But because it exists, the requirement for T
to extend Promise<unknown>
would break the recursive call, since the inferred type is not guaranteed to extend Promise
. Unless I'm missing something, there's no way around the nested conditional.
My solution:
type MyAwaited<T extends Promise<any>> = T extends Promise<infer R>
? R extends Promise<any>
? MyAwaited<R>
: R
: never
The type is guarded at root to only allow promises T extends Promise<any>
as types being passed inside it.
The first level conditional T extends Promise<infer R>
only exists to infer the type inside the promise, it isn't checking for anything. Also notice how it clearly says never
on this condition since it is impossible to be in that arm and we don't allow it.
The second conditional R extends Promise<any>
checks if R
can be further unwrapped, if not then return the value.
type MyAwaited<T extends Promise<unknown>> = T extends Promise<infer R>
? R extends Promise<unknown>
? MyAwaited<R>
: R
: T;
Or
type MyAwaited<T extends Promise<unknown>> = T extends Promise<infer R>
? R extends Promise<unknown>
? MyAwaited<R>
: R
: never;
This seems to work:
type MyAwaited<T extends Promise<any>> = T extends Promise<infer R> ? Awaited<R> : never
@AshNaz87 well, it work cause you're using Awaited
:D
it doesn't count
Actually, all the examples above do not satisfy the last one case from TS challenge (it may have been added recently):
type T = { then: (onfulfilled: (arg: number) => any) => any }
Expect<Equal<MyAwaited<T>, number>>,
So my solution is:
type GPromise = { then: (onfulfilled: (...args: any) => any) => any };
type MyAwaited<T> = T extends Promise<infer R>
? R extends Promise<infer L>
? MyAwaited<L>
: R
: T extends GPromise
? Parameters<Parameters<T['then']>[number]>[number]
: T;
I can't believe this challenge is considered easy.
Thanks everybody for the explanations.
Thanks @misterhomer1992 for help to pass the last test - perhaps last test was added recently.
We can simplify the type a little further because the last type T is satisfied by PromiseLike
type MyAwaited<T extends PromiseLike<any>> = T extends PromiseLike<infer U>
? U extends PromiseLike<unknown> // Is nested PromiseLike?
? MyAwaited<U> // Yes, further unwrap type inside PromiseLike
: U // Not PromiseLike, return type
: never; // Type error
type MyAwaited<T extends PromiseLike<any>> = T extends PromiseLike<infer R> ? Awaited<R> : T
I hate <any>
types, therefore i wrote this code.
type MyAwaited<T> = T extends Promise<infer A > ?
A extends Promise<infer B > ?
B extends Promise<infer C > ?
C extends Promise<infer D > ? D : C : B: A :
T extends {then: (onfulfilled: (arg: number) => any) => any} ? number : T
Hi @agb , I dislike the any type as well but your solution fails in at least 2 cases.
// @ts-expect-error
type error = MyAwaited<number>
// Fails to unwrap more than 4 levels of nested promises
type Z2 = Promise<Promise<Promise<Promise<Promise<string | boolean>>>>>
Expect<Equal<MyAwaited<Z2>, string | boolean>>
To be fair, the last @ts-expect-error test may be considered invalid since the built-in Awaited
// Fails
// @ts-expect-error
type error = Awaited<number>
Awaited
This project is aimed at helping you better understand how the type system works, writing your own utilities, or just having fun with the challenges.
https://ghaiklor.github.io/type-challenges-solutions/en/easy-awaited.html