[TypeScript] Type Challenges #20 - Promise.all

53 阅读3分钟

题目描述

给函数PromiseAll指定类型,它接受元素为 Promise 或者类似 Promise 的对象的数组,返回值应为Promise<T>,其中T是这些 Promise 的结果组成的数组。

const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise<string>((resolve, reject) => {
  setTimeout(resolve, 100'foo');
});

// 应推导出 `Promise<[number, 42, string]>`
const p = PromiseAll([promise1, promise2, promise3] as const)

题解

// ============= Test Cases =============
import type { EqualExpect } from './test-utils'

const promiseAllTest1 = PromiseAll([123as const)
const promiseAllTest2 = PromiseAll([12Promise.resolve(3)] as const)
const promiseAllTest3 = PromiseAll([12Promise.resolve(3)])
const promiseAllTest4 = PromiseAll<Array<number | Promise<number>>>([123])

type cases = [
  Expect<Equal<typeof promiseAllTest1, Promise<[123]>>>,
  Expect<Equal<typeof promiseAllTest2, Promise<[12number]>>>,
  Expect<Equal<typeof promiseAllTest3, Promise<[numbernumbernumber]>>>,
  Expect<Equal<typeof promiseAllTest4, Promise<number[]>>>,
]

// ============= Your Code Here =============
type MyAwaited<T> = T extends PromiseLike<infer R> ? R : T; 

declare function PromiseAll(values: any): any

代码实现

下面是实现PromiseAll类型的过程:

初始代码

declare function PromiseAll(values: any): any

这是初始的PromiseAll类型定义,它接受一个 values,并返回any类型

引入泛型

首先引入泛型T,并用T extends unknown[]将其约束为数组类型

使用readonly [...T]作为 values 的类型,确保 values 是一个只读的元组类型

这样做的目的是,当传入一个元组时,TypeScript 会保留元组元素的精确类型,而不是把它们推断为一个更宽泛的联合类型。例如,如果你传入[1, 2, 3] as const,TypeScript 会将其视为[1, 2, 3]而不是number[]

declare function PromiseAll<T extends unknown[]>(valuesreadonly [...T]): any

使用映射类型创建元组

使用映射类型{ [K in keyof T]: T[K] }创建元组

[K in keyof T]遍历T的索引,T[K]表示对应索引元素的类型

declare function PromiseAll<T extends unknown[]>(valuesreadonly [...T]): 
  Promise<{ [K in keyof T]: T[K] }>

到这里promiseAllTest1测试通过了,接着查看promiseAllTest2它推断出来的类型是Promise<[1, 2, Promise<number>]>,而我们的期望是Promise<[1, 2, number]>

对于Promise类型的元素,我们需要它的返回值的类型解析出来

解析 Promise 类型

还记得 189 - Awaited 吗?使用MyAwaited解析出嵌套的 Promise 的返回类型

type MyAwaited<T> = T extends PromiseLike<infer R> ? R : T

declare function PromiseAll<T extends unknown[]>(valuesreadonly [...T]): 
  Promise<{ [K in keyof T]: MyAwaited<T[K]> }>

对于结果元组的每个元素T[K],使用MyAwaited<T[K]>来解析Promise元素。如果元素是PromisePromiseLike对象,我们会得到它的返回值的类型,而不是Promise本身

分布式条件类型解释

为什么不直接在PromiseAll函数中使用内联推断元素的类型,而是单独使用MyAwaited呢?

declare function PromiseAll<T extends unknown[]>(valuesreadonly [...T]): 
  Promise<{ 
    [K in keyof T]: T[K] extends PromiseLike<infer R>
      ? R
      : T[K]
  }>;

解释:

这里涉及到分布式条件类型的问题。当T是联合类型时,如number | Promise<number>,使用内联推断会导致问题

内联推断的结果会是:

number | Promise<numberextends PromiseLike<infer R> ? R : number | Promise<number>

对于这种联合类型,内联推断会将整个联合类型作为一个整体进行判断,而不是分别处理联合类型中的每个成员。所以,它无法正确地将Promise<number>解析为number

而使用MyAwaited<number | Promise<number>>会分别处理联合类型的每个部分:

  • • MyAwaited<number>会返回number
  • • MyAwaited<Promise<number>>会返回number(通过PromiseLike<nfer R>推断出Rnumber

这样,最终结果会是number,这正是我们想要的结果