【问题标题】:How to unwrap the type of a Promise?如何解开 Promise 的类型?
【发布时间】:2018-06-09 05:31:48
【问题描述】:

假设我有以下代码:

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

const promisedOne = promiseOne()

typeof promisedOne // => Promised<number>

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

【问题讨论】:

  • 出于好奇,为什么你需要这样做而不是在获得价值后使用typeof
  • @FrankModica:我需要一个类属性的类型,该类型在承诺返回之前为空。 @ EricKing 一切都需要包装在一个异步函数中,这不是一个选项。
  • 在运行时,promise 不知道将来会解决什么问题(想象一下等待来自 AJAX 调用的数据)。因此,您似乎在运行时需要 TypeScript 元数据。我不确定如何做到这一点,但我知道 Angular 的依赖注入使用 TypeScript 元数据来确定要实例化和注入的类。所以也许有关这方面的文章会给你一些想法,告诉你如何完成你所需要的。

标签: typescript 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。这对于正确处理用户定义的等待对象很重要。

这使用conditional type 来检查T 是否看起来像一个promise,如果是则打开它。但是,这将不正确地处理Promise&lt;Promise&lt;string&gt;&gt;,将其解包到Promise&lt;string&gt;。等待一个 Promise 永远不会给出第二个 Promise,因此更好的定义是递归地解开 Promise。

type Awaited<T> = T extends PromiseLike<infer U> ? Awaited<U> : T
// Awaited<Promise<Promise<number>>> = number

TypeScript 4.5 (source) 使用的定义比这更复杂,以涵盖不适用于大多数用例的边缘情况,但它可以放入任何 TypeScript 4.1+ 项目中。

TypeScript 2.8 到 4.0

在 TypeScript 4.1 之前,该语言没有有意支持递归类型别名。上面显示的简单版本仍然可以工作,但递归解决方案需要一个额外的对象类型来欺骗编译器认为该类型不是递归的,然后使用indexed access type 提取我们想要的属性。

type Awaited<T> = T extends PromiseLike<infer U>
  ? { 0: Awaited<U>; 1: U }[U extends PromiseLike<any> ? 0 : 1]
  : T

官方不支持,但实际上完全没问题。

2.8 之前的 TypeScript

在 TypeScript 2.8 之前,这是不可能的。在没有 2.8 中引入的条件类型的情况下展开类似 Promise 的类型将要求泛型类型在对象上可用,以便可以使用索引访问类型来获取值。

如果我们将类型的范围限制为单级承诺,并且只接受承诺,那么在 2.8 中通过使用declaration merging 向全局Promise&lt;T&gt; 接口添加一个属性在技术上是可行的。

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

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

【讨论】:

  • 很好的实现,这对我帮助很大
  • 这很漂亮,非常感谢,正是我想要的!
【解决方案2】:

一个老问题,但我想加我的 2 美分。使用Promise 类型解包promise 很方便,但我发现我通常对找出“thenable”对象的值很感兴趣,就像await 运算符所做的那样。

为此,我编写了一个 Await&lt;T&gt; 助手,它解开 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

【讨论】:

  • 这太棒了!我添加了一个小的用法示例。
  • 太棒了,正是我想要的。谢谢!
  • 注意PromiseLike 类型的存在,这可能有助于简化您的代码。
【解决方案3】:

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

import {PromiseValue} from 'type-fest';

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

PromiseValue<promiseOne()>
//=> number

【讨论】:

    【解决方案4】:

    Typescript 3.9 计划实现此功能。不幸的是,它已被移回,它可能会在 4.0 版本中。

    新类型将被称为awaited。由于向后兼容性问题,它被延迟了。

    A related discussion on GitHub.

    【讨论】:

      【解决方案5】:

      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
      

      【讨论】:

        【解决方案6】:

        首先TypeScript4.1 release notes有这个sn-p:

        type Awaited<T> = T extends PromiseLike<infer U> ? Awaited<U> : T;
        

        它允许您解决诸如 const x = (url: string) =&gt; fetch(url).then(x =&gt; 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
        

        【讨论】:

        • 知道如何编写 AwaitReturnType 帮助程序以跳过示例中的步骤吗?
        • @j_d type AwaitReturnType&lt;F extends (...args: any) =&gt; any&gt; = Await&lt;ReturnType&lt;F&gt;&gt; 用法:AwaitReturnType&lt;typeof someFunction&gt;
        【解决方案7】:

        我们可以查看Promise的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;
        

        让我们分解一下:

        Promise&lt;string&gt;['then'] 一个接受回调的函数。让我们获取那个函数的参数。
        Parameters&lt;Promise&lt;string&gt;['then']&gt;[0]then的第一个参数:它的回调。这是可选的,所以我们只需要将联合范围缩小到真值类型。
        Parameters&lt;Promise&lt;string&gt;['then']&gt;[0] &amp; {} 回调本身。这是一个函数;让我们获取它的参数。
        Parameters&lt;Parameters&lt;Promise&lt;string&gt;['then']&gt;[0] &amp; {}&gt;[0] 回调的第一个参数:Promise 解析的值。这就是我们一直在寻找的。​​p>

        鉴于上面的条件类型,也许这个答案似乎是多余的,但我发现它对于解决 JSDoc 中的 Promise 很有用,在那里(我认为)不可能定义新的实用程序类型,但 是 em> 可以使用现有的:

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

        【讨论】:

          【解决方案8】:

          分两步:

          // 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 的答案,我不想更改,但消除了额外的 &lt;ReturnType&gt; 部分,并且(对我而言)更容易阅读。

          【讨论】:

            猜你喜欢
            • 2018-08-03
            • 2020-06-24
            • 2021-08-07
            • 1970-01-01
            • 2018-01-05
            • 1970-01-01
            • 2020-02-01
            • 1970-01-01
            • 2016-09-18
            相关资源
            最近更新 更多