[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>>
複製代碼

結語

人是菜雞,共同進步,若有錯誤,多多指教。

相關文章
相關標籤/搜索