typescript 类型体操 之 20-medium-promise-all

372 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第15天,点击查看活动详情

前言

在学习typescript的过程当中,有一个github库对其类型的学习特别有帮助,是一个有点类似于leetcode的刷题项目,能够在里面刷各种关于typescript类型的题目,在上一篇文章中,我们完成了中等的第八题,今天来做中等的第九题 20-medium-promise-all

下面这个是类型体操github仓库:

type-challenges/type-challenges: Collection of TypeScript type challenges with online judge (github.com)

20-medium-promise-all

image.png

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]>>>,
]

从README和测试用例中能够得出,题目需要我们为 PromiseAll 这个函数的入参和返回值定义类型,在这之前,我们要先来知道一下关于 Promise 的一些类型相关知识。

TS 之 Promise

在 JS 当中,Promise 有着两种状态值,一种是成功状态 resolve 另一种是失败状态 reject。

这里举两种定义一个 Promise 对象的方法,一种是通过 new 关键字创建一个新的 Promise 对象,这时候需要传入一个函数,这个函数有着两个返回值,分别对应成功回调和失败回调。

let d2 = new Promise((resolve,reject) => {
  resolve(1)
  reject(2)
})
console.log(d2);

image.png

另一种就是通过 Promise.resolve 或者 Promise.reject 直接创建

let d1 = Promise.resolve(3);
console.log(d1);

image.png

let d3 = Promise.reject(2);
console.log(d3);

image.png

那么,Promise 的这两种状态,它的类型都是什么呢,我们要怎么去获取它的类型。

let d1 = Promise.resolve(3);
console.log(d1);

let d3 = Promise.reject(2);
console.log(d3);

type D1 = typeof d1
// type D1 = Promise<number>

type D3 = typeof d3
// type D3 = Promise<never>

可以看到,对于成功状态来说,成功回调的值的类型就是这个 Promise 中的类型,然而对于失败回调来说,返回的就是 never 代表的就是永远不会到达的类型。

那么我们就可以通过之前说过的 infer 关键字来获取,

let d1 = Promise.resolve(3);
console.log(d1);

let d3 = Promise.reject(2);
console.log(d3);

type D1 = typeof d1
// type D1 = Promise<number>

type D3 = typeof d3
// type D3 = Promise<never>

type D2 = D1 extends Promise<infer R>?R:never
// type D2 = number

type D4 = D3 extends Promise<infer R>?R:never
// type D4 = never

实现 PromiseAll

从测试 case 中,我们能够看出来,这个 PromiseAll 函数,它会把数组入参放入一个 Promise 中返回,三个测试中,第一个是纯数组,并且加上了 as const ,在之前我们介绍过,加上了 as const 的类型,就会加上 readonly 修饰符。

let d5 = [1, 2, 3] as const

type D5 = typeof d5
// type D5 = readonly [1, 2, 3]

所以对于 PromiseAll 函数这个函数,我们的入参类型,就需要加上 readonly 限制,不然就会报赋值错误。

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

image.png

所以需要加上 readonly 约束。

declare function PromiseAll<T extends unknown[]>(
  values: readonly [...T]
): any;

然后就需要对于函数的返回值来进行处理,通过 T 我们能够拿到入参的数组,然后需要将它包装一层 Promise 作为返回值。

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

const promiseAllTest1 = PromiseAll([1, 2, 3] as const);
// const promiseAllTest1: Promise<[1, 2, 3]>

这样就能够完成题目的第一个要求,但是注意到测试 case 中,数组中还会存在 Promise对象 的情况,碰到这种类型的时候,我们需要将它做一层拆分,拿出 Promise 中的类型,包装进我们的函数返回值当中,这也就是 JS 中,promise.all 的作用

对于如何取出 Promise 中的返回值类型,我们在上面已经例举过了,就只需要在上面的 PromiseAll 的返回值当中再加入一个条件类型判断,是否为 Promise 类型,是的话就需要做一个拆分,不是就直接放入返回值数组。

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

这样到这里,我们的测试例子就全部都通过了。

知识点

关于上述提到了部分的知识点:

  1. Promise 的返回值类型
  2. infer 获取 Promise 返回值
  3. 条件类型

本文主要的要点在于灵活获取 Promise 中的返回值类型,以及如何利用函数的入参来推断函数的出参。

总结

今天我们做完了中等的第九题,题型比较新颖,但是对于我们日常开发的函数类型的定义也是有着很大的作用的,可以学会灵活的运用函数的入参来推断出函数的出参。