前言
TS 内置的Promise.all,在lib.es2015.promise.d.ts檔案中聲明,通過函數重載定義多個泛型進行類型聲明的。
而在最新的 TS(4.1.3) 中已經有比較優雅的方法進行聲明了,是以這篇文章的作用就是介紹怎麼寫出比較優雅一個Promise.all類型。(不包括函數實作)
前置知識
as const 聲明元組
在某個版本以前,聲明元組隻能通過[string, typeof X, number]一個個手動聲明,而現在可以通過as const進行聲明元組,用法如下:
const tuple = ['你好', '元組', 17] as const// ^^^^^ = readonly ["你好", "元組", 17]複制代碼
可以看到這樣就聲明了一個元組,之前的話就得一個個寫元組元素聲明。
映射元組
假設依舊有上面的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,是以當參數超過十個的時候就會報錯。(雖然不會有這個場景)
Promise.all行為,因為使用的泛型,是以可以不用傳入元組,傳入數組也能識别。
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的對象。
是以隻要含有.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不會。
總代碼:
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>>複制代碼
結語