# 189-实现Awaited
# 题目描述
假如我们有一个 Promise 对象,这个 Promise 对象会返回一个类型。在 TS 中,我们用 Promise<T>
中的 T 来描述这个 Promise 返回的类型。请你实现一个类型,可以获取这个类型。
例如:Promise<ExampleType>
,请你返回 ExampleType 类型。
type ExampleType = Promise<string>;
type Result = MyAwaited<ExampleType>; // string
这个挑战来自于 @maciejsikora (opens new window) 的文章:original article (opens new window)
# 分析
这个题目同 第一个元素 一样,都需要用到 A extends infer xxx
的特性,只不过原本的 infer
是匹配数组,而这里的 infer
,是去匹配 Promise
的返回值。
了解这一点后,可以非常快速的写出如下代码:
type MyAwaited<T> = T extends Promise<infer R> ? R : never;
type Case1 = MyAwaited<Promise<string>>; // string
但是实际的场景中,还会存在 Promise
嵌套的场景:
type MyAwaited<T> = T extends Promise<infer R> ? R : never;
type Case2 = MyAwaited<Promise<Promise<string>>>; // Promise<string>
此时由于嵌套,并不能得到预期的最终的返回类型。
此时就需要递归上场了,简单改写,如下:
type MyAwaited<T> = T extends Promise<infer R> ? MyAwaited<R> : T;
type Case2 = MyAwaited<Promise<Promise<string>>>; // string
type Case3 = MyAwaited<Promise<Promise<Promise<string>>>>; // string
此时不管嵌套几层,都可以解出最终的类型。(由于牵扯到了递归,个人认为不太适合放在简单里)。
# 题解
讲道理上述解法已经足够,但是在题目的 Case 中,存在如下场景:
type T = { then: (onfulfilled: (arg: number) => any) => any };
// 期望 MyAwaited<T> = number
也就是还需要处理 类似 promise 的场景,根据题目 case,可以写出如下代码:
type MyAwaited<T> = T extends
| Promise<infer R>
| { then: (onfullfilled: (arg: infer R) => any) => any }
? MyAwaited<R>
: T;
利用 |
覆盖 普通的 Promise
和 then
两种场景。
这里还有一点值得一提的是,当联合类型位于 extends
右侧时,并没有分发特性,虽然判断会做多次,但是其多次判断的结果会以或的方式合并后交由 extends
的逻辑处理,比如,'a' extends 'a' | 'b' ? 1 : 2
,此时,可以理解为会进行 'a' extends 'a'
以及 'a' extends 'b'
两次判断,两者有一处为 true 即返回 1,否则返回 2。但是并不会返回 1 | 2
。
# 知识点
A extends Promise<infer R>
,匹配推断类型- 递归解决嵌套问题
- 联合类型位于
extends
右侧时不分发
# 2024-02-13 补充
由于用例中增加了这一错误用例:
// @ts-expect-error
type error = MyAwaited<number>;
这就需要对 入参的范型 进行类型限制,可以写出如下代码:
type MyAwaited<T extends Promise<any>> = T extends
| Promise<infer R>
| { then: (onfullfilled: (arg: infer R) => any) => any }
? R extends Priomise<any>
? MyAwaited<R>
: R
: T;
此时由于 type T = { then: (onfulfilled: (arg: number) => any) => any };
这一用例的存在,使得单纯的 Promise<any>
依旧失效,此时可以提取出一个新的类型,MyPromiseLike
来进行替换。
最终的代码如下:
type MyPromiseLike<T> =
| Promise<T>
| { then: (onfullfilled: (arg: T) => any) => any };
type MyAwaited<T extends MyPromiseLike<any>> = T extends MyPromiseLike<infer R>
? R extends MyPromiseLike<any>
? MyAwaited<R>
: R
: never;
此时便可覆盖所有的用例。
← 43-实现Exclude 268-实现If →