# 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;

利用 | 覆盖 普通的 Promisethen 两种场景。

这里还有一点值得一提的是,当联合类型位于 extends 右侧时,并没有分发特性,虽然判断会做多次,但是其多次判断的结果会以或的方式合并后交由 extends 的逻辑处理,比如,'a' extends 'a' | 'b' ? 1 : 2,此时,可以理解为会进行 'a' extends 'a' 以及 'a' extends 'b'两次判断,两者有一处为 true 即返回 1,否则返回 2。但是并不会返回 1 | 2

# 知识点

  1. A extends Promise<infer R>,匹配推断类型
  2. 递归解决嵌套问题
  3. 联合类型位于 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;

此时便可覆盖所有的用例。

Last Updated: 2024/2/13 04:54:47