在 TypeScript 中实现 Promise.all?

起因是做 type-challenges 上的一道类型题:

键入函数 PromiseAll,它接受 PromiseLike 对象数组,返回值应为 Promise<T>,其中 T 是解析的结果数组。

const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise<string>((resolve, reject) => {
  setTimeout(resolve, 100, 'foo');
});
  
// expected to be `Promise<[number, 42, string]>`
const p = PromiseAll([promise1, promise2, promise3] as const)

你的代码:

declare function PromiseAll(values: any): any

测试用例:

import type { Equal, Expect } from '@type-challenges/utils'

const promiseAllTest1 = PromiseAll([1, 2, 3] as const)
const promiseAllTest2 = PromiseAll([1, 2, Promise.resolve(3)] as const)
const promiseAllTest3 = PromiseAll([1, 2, Promise.resolve(3)])

type cases = [
  Expect<Equal<typeof promiseAllTest1, Promise<[1, 2, 3]>>>,
  Expect<Equal<typeof promiseAllTest2, Promise<[1, 2, number]>>>,
  Expect<Equal<typeof promiseAllTest3, Promise<[number, number, number]>>>,
]

有兴趣可以自己做一下,答题链接

先说下正确答案:

declare function PromiseAll<T extends any[]>(values: readonly [...T]): Promise<{
  [key in keyof T]: Awaited<T[key]>
}>

这个不难想到,从测试用例可以看到 values 应是一个只读的元组,文档中有一段描述:

Tuples can also have rest elements, which have to be an array/tuple type.(元组中可以有剩余元素,但剩余元素的类型必须为数组/元组类型)

type StringNumberBooleans = [string, number, ...boolean[]];
type StringBooleansNumber = [string, ...boolean[], number];
type BooleansStringNumber = [...boolean[], string, number];

那么就可以通过 T extends any[]readonly [...T] 来构造只读元组类型。然后遍历这个元组,再给 T[key] 套一个 Awatied<T> 解决 Promise 的问题。

问题解决了,完美啊。但又细细看了看测试用例,感觉不太对劲?(1)为什么第三个测试用例返回的是 [number, number, number] 而非 [1, 2, 3] 呢?

我又想试试别的方法,如果我让 values 是一个只读数组,map 的时候把 readonly 去掉不也行吗?

declare function PromiseAll<T extends readonly any[]>(values: T): Promise<{
  -readonly [key in keyof T]: Awaited<T[key]>
}>

然而第三个用例又过不去了...

const promiseAllTest3 = PromiseAll([1, 2, Promise.resolve(3)]) // Promise<number[]>

这次直接变成 Promise<number[]> 了,(2)这又是怎么回事?

阅读 1.3k
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题