[TS 杂谈](1)Promise.all 优雅的类型声明

前言

TS 内置的Promise.all,在lib.es2015.promise.d.ts文件中声明,经过函数重载定义多个泛型进行类型声明的。ajax

而在最新的 TS(4.1.3) 中已经有比较优雅的方法进行声明了,所以这篇文章的做用就是介绍怎么写出比较优雅一个Promise.all类型。(不包括函数实现)数组

前置知识

as const 声明元组

在某个版本之前,声明元组只能经过[string, typeof X, number]一个个手动声明,而如今能够经过as const进行声明元组,用法以下:promise

const tuple = ['你好', '元组', 17] as const
// ^^^^^ = readonly ["你好", "元组", 17]
复制代码

能够看到这样就声明了一个元组,以前的话就得一个个写元组元素声明。markdown

映射元组

假设依旧有上面的tuple变量,如今有个需求须要把tuple变量的每一个元素都转成Promise<元素>类型,而这时候就须要使用映射元组的技巧了,语法和映射类型一致。编辑器

type TuplePromise<T> = {
  [K in keyof T]: Promise<T[K]>
}
type T1 = TuplePromise<typeof tuple>
// ^^ = readonly [Promise<"你好">, Promise<"元组">, Promise<17>]
复制代码

类型实现

假设以后都有以下六个类型函数

const ajax1: Promise<string> = Promise.resolve(':)')
const ajax2: Promise<number> = Promise.resolve(17)
const ajax3: Promise<boolean> = Promise.resolve(true)
const ajax4: string = ':)'
const ajax5: number = 17
const ajax6: boolean = true

const ajaxArr = [ajax1, ajax2, ajax3, ajax4, ajax5, ajax6] as const
复制代码

原生 Promise.all 类型

lib.es2015.promise.d.ts文件中能够找到对应的函数声明,建议经过 VSC 编辑器中使用ctrl+鼠标左键Promise.all跳转定义,定义以下图。测试

如图所示

能够看到源码类型是经过使用泛型T进行类型声明的,源码中最多参数只能有10个,由于定义的重载只有10个,最后一个就是T1-T10,因此当参数超过十个的时候就会报错。(虽然不会有这个场景)ui

Promise.all行为,由于使用的泛型,所以能够不用传入元组,传入数组也能识别。spa

Promise.all([ajax1, ajax2, ajax3, ajax4, ajax5, ajax6])
// 这是运行时类型 Promise.all([Promise<string>, Promise<number>, Promise<boolean>, string, number, boolean])
// 返回 Promise<[string, number, boolean, string, number, boolean]>
  .then(res => {})
  // res: [string, number, boolean, string, number, boolean]
复制代码

能够看到是Promise的话就会拆出里面.then参数的类型,若是不是则原样返回。经过源码,咱们能够看出是用PromiseLike的类型来进行拆解的,这是由于Promise.all能够使用含有.then的对象。code

所以只要含有.then方法,就要拆出方法参数的类型。

myPromiseAll 类型实现

myPromiseAll类型只能接受一个元组参数,而后经过元组映射进行拆解,最后返回Promise<元组映射结果>

由上一节能够得出咱们须要一个类型来提取.then的方法参数类型,这个很简单,能够使用内置的PromiseLike类型判断是否含有.then方法且还会自动获取方法参数类型,所以经过infer能够轻松取出来。

type GetPromiseLikeThenParam<T> = T extends PromiseLike<infer U> ? U : T
type GPLTP<T> = GetPromiseLikeThenParam<T>
// 测试
type T1 = GPLTP<typeof ajax1>
//   ^^ = string
type T2 = GPLTP<typeof ajax4>
//   ^^ = string
复制代码

映射元组类型进行提取元组每个PromieLike类型。

type ExtractTuplePromiseLike<T extends ReadonlyArray<unknown>> = {
  [K in keyof T]: GPLTP<T[K]>
}
type ETPL<T extends ReadonlyArray<unknown>> = ExtractTuplePromiseLike<T>

type T1 = ETPL<typeof ajaxArr>
复制代码

ReadonlyArray<unknown> 至关于 readonly unknown[]

基础类型准备就绪,接下来就是写函数声明。

函数参数是一个元组,所以声明参数为ReadonlyArray<unknown>,因为返回的类型与函数参数有关,所以函数参数要声明为泛型T,而后返回就是经过上面的ETPL提取T,而后再用Promise包装就成功写好myPromiseAll函数类型

declare function myPromiseAll<T extends ReadonlyArray<unknown>>(
  tuple: T,
): Promise<ETPL<T>>
// 测试
myPromiseAll(ajaxArr)
  .then((res) => {})
  //     ^^^ = readonly [string, number, boolean, string, number, true]
复制代码

Promise.all的区别是多了个readonly和变量使用时须要用到as const,若是为了方即可以这么写:

myPromiseAll([ajax1, ajax2, ajax3, ajax4, ajax5, ajax6] as const).then((res) => {})
复制代码

也是彻底没有问题。

总结

myPromiseAll相较于Promise.all的类型仍是有些区别的,Promise.all在数组长度超过10的时候会报错而myPromiseAll不会。

myPromiseAll须要经过as const进行参数声明传入元组,Promise.all不须要。

myPromiseAll返回的Promise<readonly [元组元素]Promise.all返回的Promsie<[元组元素]

总代码:

type GetPromiseLikeThenParam<T> = T extends PromiseLike<infer U> ? U : T
type GPLTP<T> = GetPromiseLikeThenParam<T>

type ExtractTuplePromiseLike<T extends ReadonlyArray<unknown>> = {
  [K in keyof T]: GPLTP<T[K]>
}
type ETPL<T extends ReadonlyArray<unknown>> = ExtractTuplePromiseLike<T>

declare function myPromiseAll<T extends ReadonlyArray<unknown>>(
  tuple: T,
): Promise<ETPL<T>>
复制代码

结语

人是菜鸡,共同进步,若有错误,多多指教。

相关文章
相关标签/搜索