如何打开类型的一个承诺?

假设我有以下代码:

async promiseOne() {
return 1
} // => Promise<number>


const promisedOne = promiseOne()


typeof promisedOne // => Promised<number>

如何将承诺结果的类型(在这个简化的例子中是一个数字)提取为它自己的类型?

58206 次浏览

字体脚本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<string>>,将其展开为 Promise<string>。等待一个承诺永远不会给出第二个承诺,所以一个更好的定义是递归展开承诺。

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

官方并不支持这种做法,但在实践中,这种做法完全没有问题。

2.8之前的打字脚本

在 TypeScript 2.8之前,这是不可能的。如果不使用2.8中引入的条件类型来展开类似于承诺的类型,则需要在对象上使用泛型类型,以便可以使用索引访问类型来获取值。

如果我们将类型的范围限制在一个单一的承诺级别,并且只接受承诺,那么在2.8中通过使用 声明合并向全局 Promise<T>接口添加一个属性来实现这一点在技术上是可能的。

interface Promise<T> {
__promiseValue: T
}
type Awaited<T extends Promise<any>> = T["__promiseValue"]


type T = Awaited<Promise<string>> // => string

更新

在 TypeScript 4.5中,我们现在可以使用 Awaited关键字,TmTron 在他们的答案中写道。

我鼓励您查看 Awaited的源代码,它是递归的,并且有一些我的解决方案没有捕捉到的非常有趣的边缘情况。

原创的

这是个老问题了,但我想补充一下。使用 Promise类型展开承诺是很方便的,但是我发现我通常对计算“ thable”对象的值很感兴趣,就像 await操作符所做的那样。

为此,我编写了一个 Await<T>助手,它可以拆开承诺和可变对象:

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

如果可以访问 npm 模块,则可以使用 type-fest:

import {PromiseValue} from 'type-fest';


async promiseOne() {
return 1
} // => () => Promise<number>


PromiseValue<promiseOne()>
//=> number

类型脚本3.9计划实现这个特性。不幸的是,它已经被移回到了4.0版本中。

新类型将被称为类似于 awaited的东西。由于向后兼容性问题而延迟。

关于 GitHub 的相关讨论。

更新(类型脚本 > = 4.5)

因为类型脚本4.5,我们可以使用 Awaited: 请参见 < a href = “ https://www.typeescriptlang.org/docs/handbook/Release-note/typeescript-4.5.html # the-waiting-type-and-晃荡的改进”rel = “ norefrer”> 类型脚本发布 注释

const promise = new Promise<string>((res, rej) => res('x'))
type test0 = Awaited<typeof promise>  // string
type test1 = Awaited<Promise<number>> // number

Stackblitz 示例

原创的

工具腰带 lib 包含 承诺

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

首先,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

我们可以通过查看  焊  then函数的参数来推断类型。

type PromisedString = Promise<string>;
type AwaitedPromisedString = Parameters<Parameters<PromisedString['then']>[0] & {}>[0];


const promisedString: PromisedString = Promise.resolve('sup');
const x: AwaitedPromisedString = await promisedString;
// assignment succeeds!
const y: string = x;

我们可以归纳为一种实用工具类型:

type Awaited<T extends Promise<unknown>> = Parameters<Parameters<T['then']>[0] & {}>[0];
const promisedString: Promise<string> = Promise.resolve('sup');
const x: Awaited<typeof promisedString> = await promisedString;
const y: string = x;

我们来分析一下:

一个接受回调函数的函数。让我们得到这个函数的参数。
Parameters<Promise<string>['then']>[0] then的第一个参数: 它的回调。这是可选的,所以我们需要将联合仅限于真实类型。
它是一个函数,让我们获取它的参数。
Parameters<Parameters<Promise<string>['then']>[0] & {}>[0]回调函数的第一个参数: 用于解析  承诺的值。这就是我们要找的。

也许从上面的条件类型来看,这个答案似乎是多余的,但是我发现它对于解析 JSDoc 中的誓言是有用的,因为在 JSDoc 中不可能(我认为)定义新的实用程序类型,但是 可以使用现有的实用程序类型:

/** @param {Parameters<Parameters<ReturnType<import('box2d-wasm')>['then']>[0] & {}>[0]} box2D */
export const demo = box2D => {
};

分两步:

// First, define a type for async functions returning V
type AsyncFunction<V> = (...args: never[]) => PromiseLike<V>


// Second, use the AsyncFunction + infer to define AsyncReturnType
type AsyncReturnType<V> = V extends AsyncFunction<infer U> ? U : V

在你的情况下,你可以这样做:

let foo: AsyncReturnType<typeof promiseOne>

如果你只是使用 AsyncReturnType,你不需要导出 AsyncFunction...argsnever,因为你忽略了它们。

这个答案类似于 Evan 的,我不想改变,但是去掉了额外的 <ReturnType>部分,(对我来说)更容易阅读。