typescript - 如何解開 Promise 的類型?
假設我有以下程式碼:
async promiseOne() {
return 1
} // => Promise<number>
const promisedOne = promiseOne()
typeof promisedOne // => Promised<number>
我該如何提取 promise 結果的類型(在這個簡化的情況下是一個數字)作為它自身的類型?
答案#1
TypeScript 4.5
Awaited 類型現在已內建於語言中,因此你不需要自己撰寫一個。
type T = Awaited<Promise<PromiseLike<number>> // => number
TypeScript 4.1 到 4.4
您可以自己實現這個,有幾種可能的定義。最簡單的是:
type Awaited<T> = T extends PromiseLike<infer U> ? U : T
// Awaited<Promise<number>> = number
請注意,這種類型使用的是 PromiseLike 而不是 Promise。這對正確處理使用者定義的可等待對象非常重要。
這使用條件型別來檢查 T 是否看起來像一個 promise,如果是的話就將其解封。然而,這會不正確地處理 Promise<Promise<string>>,將其解封為 Promise<string>。等待一個 promise 永遠不會得到第二個 promise,因此更好的定義是遞迴地解封 promises。
type Awaited<T> = T extends PromiseLike<infer U> ? Awaited<U> : T
// Awaited<Promise<Promise<number>>> = number
TypeScript 4.5(源碼)使用的定義比這個更複雜,以涵蓋大多數使用案例不適用的邊緣情況,但它可以直接用於任何 TypeScript 4.1+ 專案。
TypeScript 2.8 到 4.0
在 TypeScript 4.1 之前,語言並不刻意支援遞迴型別別名。上面展示的簡單版本仍然可以運作,但遞迴解法需要額外的物件型別來欺騙編譯器,使其認為該型別不是遞迴的,然後使用索引存取型別取出我們想要的屬性。
type Awaited<T> = T extends PromiseLike<infer U>
? { 0: Awaited<U>; 1: U }[U extends PromiseLike<any> ? 0 : 1]
: T
這在官方上不被支持,但實際上完全沒問題。
TypeScript 2.8 之前
在 TypeScript 2.8 之前這是無法直接做到的。在沒有 2.8 引入的條件型別的情況下解開類似 Promise 的型別,將需要該泛型型別在物件上是可用的,以便可以使用索引訪問型別來取得值。
如果我們將類型的範圍限制在單層的 Promise,並且只接受 Promise,那麼在 2.8 版本中,通過使用宣告合併向全域的 Promise<T> 介面添加屬性,從技術上是可行的。
interface Promise<T> {
__promiseValue: T
}
type Awaited<T extends Promise<any>> = T["__promiseValue"]
type T = Awaited<Promise<string>> // => string
答案#2
更新
從 TypeScript 4.5 開始,我們現在可以使用 Awaited 關鍵字,正如 TmTron 在他們的回答中所寫的那樣。
我鼓勵你查看 Awaited 的原始程式碼,它是遞迴的,並且捕捉了一些非常有趣的邊緣案例,而我的解法未涉及這些案例。
原始
這是一個老問題,但我想加入我的兩分意見。使用 Promise 類型來解包一個承諾非常方便,但我發現我通常更想了解的是如何獲取一個「thenable」對象的值,就像 await 運算符所做的那樣。
為此,我寫了一個 Await<T> 幫助器,它可以解開 Promise 以及 thenable 對象:
type Await<T> = T extends {
then(onfulfilled?: (value: infer U) => unknown): unknown;
} ? U : T;
像這樣使用它:
const testPromise = async () => 42
type t1 = Await<ReturnType<typeof testPromise>>
// t1 => number
答案#3
首先,TypeScript 4.1 的發行說明中有這段摘錄:
type Awaited<T> = T extends PromiseLike<infer U> ? Awaited<U> : T;
這讓你可以解決像 const x=(url: string) => fetch(url).then(x => x.json()) 這樣的問題,但結合 Jon Jaques 的答案和 ErikE 的評論,並且假設你使用的是 4.1 之前的版本,我們得到:
type Await<T> = T extends PromiseLike<infer U> ? U : T
例如:
const testPromise = async () => 42
type t1 = Await<ReturnType<typeof testPromise>>
// t1 => number
答案#4
更新 (typescript >= 4.5)
自從 TypeScript 4.5 我們可以使用 Awaited:參見 TypeScript 發行說明
const promise = new Promise<string>((res, rej) => res('x'))
type test0 = Awaited<typeof promise> // string
type test1 = Awaited<Promise<number>> // number
Stackblitz 範例
原始
ts-toolbelt 庫有 PromiseOf
import {C} from 'ts-toolbelt'
const promise = new Promise<string>((res, rej) => res('x'))
type test0 = C.PromiseOf<typeof promise> // string
type test1 = C.PromiseOf<Promise<number>> // number
