TS 内置的Promise.all
,在lib.es2015.promise.d.ts
文件中声明,经过函数重载定义多个泛型进行类型声明的。ajax
而在最新的 TS(4.1.3) 中已经有比较优雅的方法进行声明了,所以这篇文章的做用就是介绍怎么写出比较优雅一个Promise.all
类型。(不包括函数实现)数组
在某个版本之前,声明元组只能经过[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
复制代码
在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
类型只能接受一个元组参数,而后经过元组映射进行拆解,最后返回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>>
复制代码
人是菜鸡,共同进步,若有错误,多多指教。