持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第15天,点击查看活动详情
前言
在学习typescript的过程当中,有一个github库对其类型的学习特别有帮助,是一个有点类似于leetcode的刷题项目,能够在里面刷各种关于typescript类型的题目,在上一篇文章中,我们完成了中等的第八题,今天来做中等的第九题 20-medium-promise-all
下面这个是类型体操github仓库:
20-medium-promise-all
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);
另一种就是通过 Promise.resolve
或者 Promise.reject
直接创建
let d1 = Promise.resolve(3);
console.log(d1);
let d3 = Promise.reject(2);
console.log(d3);
那么,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;
所以需要加上 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] }>;
这样到这里,我们的测试例子就全部都通过了。
知识点
关于上述提到了部分的知识点:
- Promise 的返回值类型
- infer 获取 Promise 返回值
- 条件类型
本文主要的要点在于灵活获取 Promise 中的返回值类型,以及如何利用函数的入参来推断函数的出参。
总结
今天我们做完了中等的第九题,题型比较新颖,但是对于我们日常开发的函数类型的定义也是有着很大的作用的,可以学会灵活的运用函数的入参来推断出函数的出参。