【问题标题】:Typescript typings for failure `reason` in various Promises implementations?各种 Promises 实现中失败“原因”的打字稿类型?
【发布时间】:2015-03-27 05:22:26
【问题描述】:

各种 promise 库的当前 d.ts 定义文件似乎放弃了提供给失败回调的数据类型。

when.d.ts:

interface Deferred<T> {
    notify(update: any): void;
    promise: Promise<T>;
    reject(reason: any): void;
    resolve(value?: T): void;
    resolve(value?: Promise<T>): void;
}

q.d.ts:

interface Deferred<T> {
    promise: Promise<T>;
    resolve(value: T): void;
    reject(reason: any): void;
    notify(value: any): void;
    makeNodeResolver(): (reason: any, value: T) => void;
}

jquery.d.ts (promise-ish):

fail(failCallback1?: JQueryPromiseCallback<any>|JQueryPromiseCallback<any>[], ...failCallbacksN: Array<JQueryPromiseCallback<any>|JQueryPromiseCallback<any>[]>): JQueryPromise<T>;

我在Promises/A+ spec 中没有看到任何提示我reason 无法输入的内容。

我确实在 q.d.ts 上尝试过,但是类型信息似乎在从 'T'U 的转换发生时丢失了,我不完全理解为什么必须这样案例 - 我的尝试(机械地将 'N'F 类型参数添加到 &lt;T&gt;'O'G 类型参数到 &lt;U&gt; 并输入我认为应该是的东西)主要是在 @ 987654335@为新增类型参数的类型。

有没有理由不能给它们自己的类型参数?是否存在可以完全类型化的 Promise 构造?

【问题讨论】:

    标签: typescript promise type-safety


    【解决方案1】:

    哎呀,这真的很难。这实际上是一个很好的问题。

    让我从一个开始

    是的,可以打字

    创建一个将异常考虑在内的 Promise 类型是完全可能的。当我用类型化语言实现一个 Promise 库时,我从 Promise&lt;T,E&gt; 类型开始,后来又恢复为 Promise&lt;T&gt; - 它有效,但并不有趣。你要的东西有名字。

    检查的异常

    您在这里实际要求的是检查异常 - 这是一个函数必须声明它可能抛出的异常类型 - 有些语言实际上可以很好地处理异常。 .. 有 a 语言可以做到这一点 - Java。在 Java 中,当您有一个可能抛出异常(RuntimeException 除外)的方法时,它必须声明它:

     public T foo() throws E {}
    

    请看,在 Java 中 - 返回类型和错误类型都是方法签名的一部分。这是一个有争议的选择,很多人觉得它很乏味。它在其他语言的开发人员中非常不受欢迎,因为它会迫使您编写大量笨拙的代码。

    假设您有一个函数返回一个承诺,该承诺建立一个数据库连接以获取一个 URL,发出一个 Web 请求并将其写入文件。与您在 Java 中所拥有的 Promise 等效的内容类似于:

    Promise<T, FileAccessError | DatabaseError | WebRequestError | WebConnectionError | TypeError>
    

    多次键入并不是很有趣 - 因此这些语言(如 C#)中的异常类型通常是隐含的。也就是说,如果您喜欢这种风格选择,您绝对应该这样做。这并不是微不足道的——promise 的类型已经相当复杂了:

    then<T,U> :: Promise<T> -> T -> Promise<U> | U -> Promise<U>
    

    这就是then 所做的——它接受一个 T 类型的 Promise,以及一个接受 T 并返回一个值 (U) 或一个值的 Promise (Promise) 的回调——并返回一个 Promise(解包和转换)。从那以后,实际类型就更难了——而且这两个参数都是可选的:

    then<T,U> :: Promise<T> -> (T -> Promise<U> | U) | null) -> ((Promise<T> -> any -> Promise<U> | U) | null) -> Promise<U>
    

    如果您在其中添加错误处理,这将变得非常“有趣”,因为所有这些步骤现在都有一个额外的错误路径:

    then<T,E,U,E2> :: Promise<T,E> -> (T -> Promise<U, E2> | U) | null -> (E -> Promise<U, E2> | U) | null -> Promise<U>
    

    基本上 - then 现在有 4 个类型参数,这是人们通常想要避免的 :) 这完全有可能,但取决于你。

    【讨论】:

    • 很棒的答案!在 Typescript 中,我们不能只是一个 Promise&lt;T&gt; 扩展 Promise&lt;T, any&gt; 并且它会再次为“懒惰”感到高兴吗?另外,由于 Typescript 的类型检查是结构化的,我们可以只指出 Promise&lt;MyT, Exception&gt;Promise&lt;MyT, FullException | string | XhrOrWhatever$AjaxThrows&gt; 在最坏的情况下并期望投掷者表现自己?
    • 从技术上讲,你可以(这是 Promise 不属于语言 try/catch 语义的一部分的好处之一 - 在像 bluebird 这样的现代 Promise 库中(我看到你使用的是真的很旧的 Q)你还内置了对类型捕获和谓词捕获的支持,这使得这种处理非常容易。
    【解决方案2】:

    我认为实现这样的事情的主要挑战之一是then 的参数是可选的,它的返回类型取决于它们是否是函数。 Q.d.ts 不正确,即使只有一个类型参数:

       then<U>(onFulfill?: (value: T)  => U | IPromise<U>, 
               onReject?: (error: any) => U | IPromise<U>, 
               onProgress?: Function):                      Promise<U>;
    

    这里说Promise&lt;T&gt;.then()的返回类型是Promise&lt;U&gt;,但是如果onFulfill没有指定,返回类型其实是Promise&lt;T&gt;

    这甚至没有考虑到onFulfillonReject 都可以抛出的事实,为您提供五种不同的错误源来协调以确定p.then(onFulfill, onReject) 的类型:

    • 如果未指定onReject,则p 的拒绝值
    • onFulfill 中引发错误
    • onFulfill 返回的 promise 的拒绝值
    • onReject 中引发错误
    • onReject 返回的 promise 的拒绝值

    我很确定在 TypeScript 中甚至没有办法表达项目符号 2 和 4,因为它没有检查异常。

    如果我们用同步代码来类比,一个代码块的结果值可以很好地定义。一段代码可能引发的一组错误很少是(除非正如 Benjamin 指出的那样,您正在使用 Java 编写代码)。

    为了进一步类比,即使使用 TypeScript 的强类型,它甚至没有提供 (AFAIK) 一种机制来指定捕获的异常的类型,因此具有 any 错误类型的承诺与 TypeScript 处理错误的方式一致同步代码。

    this page about that very matter 上的 cmets 部分包含我认为非常相关的评论:

    根据定义,异常是一种“异常”情况,可能由于多种原因(例如语法错误、堆栈溢出等)而发生。虽然这些错误中的大多数确实源自 Error 类型,但您调用的某些东西也可能会抛出任何东西。

    在同一页面上给出的不支持类型异常的原因在这里也很相关:

    由于我们对函数可能抛出的异常没有任何概念,因此允许对“catch”变量进行类型注释将具有高度误导性——它不是异常过滤器,也不是任何类似于保证类型安全的东西。

    所以我的建议是不要尝试在类型定义中确定错误类型。异常本质上是不可预测的,.then 的类型化定义已经很难按原样定义了。


    还要注意:许多包含类似 Promise 结构的强类型语言也无法表达它们可能产生的潜在错误的类型。 .NET 的 Task&lt;T&gt; 有一个结果类型参数,Scala 的 Future[T] 也是如此。 Scala 的错误封装机制Try[T](它是Future[T] 接口的一部分)除了从Throwable 继承之外,不保证其产生的错误类型。所以我会说 TypeScript 中的单一类型参数承诺很好。

    【讨论】:

    • 重量级的另一个很棒的答案!谢谢。
    • 我之所以选择这个答案,是因为它在理论方面更深入一点,但两个答案都非常出色、有帮助且值得赞赏。
    猜你喜欢
    • 2016-08-03
    • 1970-01-01
    • 1970-01-01
    • 2013-12-22
    • 1970-01-01
    • 2019-07-26
    • 2022-12-07
    • 2022-10-12
    • 2022-11-10
    相关资源
    最近更新 更多