TS中类型定义在处理复杂的异步操作的应用

8 阅读3分钟

有这样一段TS代码

type Awaited<T> = T extends PromiseLike<infer P> ? P extends PromiseLike<any> ? Awaited<P> : P : never;

看起来有点绕,接下来我们详细节讲解一下它的使用和应用场景

1. 整体类型定义结构

这是一个自定义的条件类型(Conditional Type)定义,名为 Awaited,用于处理 Promise 或者实现了 PromiseLike 接口的类型,目的是获取这个 Promise(或类似 Promise 的类型)最终被 await 之后解析出来的值的类型。

2. 条件判断部分(T extends PromiseLike<infer P>

  • PromiseLike 接口:在 TypeScript 中,PromiseLike 是一个表示类似 Promise 的类型的接口。只要一个类型具有 then 方法(就像标准的 Promise 那样,其 then 方法用于添加成功和失败的回调),就可以认为它是 PromiseLike 类型。
  • infer 关键字infer 用于在条件类型中做类型推断。在这里,infer P 的作用是从满足 T extends PromiseLike<...> 这个条件的类型 T 中,尝试推断出 Promise 所包含的内部值的类型,并将其命名为 P。例如,如果 T 是 Promise<number>,那么通过 infer 就能推断出 P 为 number

3. 嵌套条件判断(P extends PromiseLike<any>

  • 进一步判断通过 infer 推断出来的 P 类型是否本身也是一个 PromiseLike 类型。这是因为在 JavaScript(以及 TypeScript 遵循其异步模型)中,有可能存在多层嵌套的 Promise,比如 Promise<Promise<number>> 这种情况。
  • 如果 P 仍然是 PromiseLike 类型,那就需要继续递归地去解析它最终包含的值的类型,所以再次使用 Awaited<P> 进行递归调用,不断解开嵌套的 Promise,直到最终解析出的类型不再是 PromiseLike 类型为止。

4. 返回值部分

  • P 作为返回值:如果经过判断,P 不是 PromiseLike 类型了(也就是已经是最内层的实际值的类型了),那么就直接返回 P,这个 P 就是当前这个 Promise(或者 PromiseLike 类型)被 await 之后会得到的值的类型。
  • never 作为返回值:如果一开始的 T 类型根本就不满足 PromiseLike 类型的条件(也就是不扩展自 PromiseLike),那么整个条件类型就返回 never,表示不存在这样一个可被 await 解析出有效类型的值。

5. 应用场景示例

假设有如下的异步函数返回类型:

async function getData(): Promise<Promise<string>> {
    return Promise.resolve('Hello');
}

这里 getData 函数返回的是一个嵌套的 Promise(外层 Promise 包裹着内层的 Promise<string>)。

如果我们想要获取最终 await 这个函数返回值之后实际得到的值的类型,可以使用 Awaited 类型来进行推导:

type ResultType = Awaited<ReturnType<typeof getData>>;
// ResultType 的类型会被推导为 string,因为 Awaited 类型会解析掉嵌套的 Promise,得到最终的实际值类型

这样的类型定义在处理复杂的异步操作、对异步返回值进行类型安全的处理以及构建通用的异步相关的类型工具等场景中非常有用,可以帮助开发者更精准地把握代码中的类型关系,避免因异步类型处理不当而出现的类型错误。