各种 Promises 实现中失败“原因”的打字稿类型?

Typescript typings for failure `reason` in various Promises implementations?

各种 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(承诺):

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 类型参数添加到 <T>'O'G 将参数键入 <U> 并按我认为应该的方式键入内容)主要导致 {} 成为新添加的类型参数的类型。

为什么不能给它们自己的类型参数?有没有可以完全键入的 promise 构造?

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

让我先说一个

是的,可以打字

完全有可能创建一个将异常考虑在内的承诺类型。当我用类型化语言实现一个 promise 库时,我从 Promise<T,E> 类型开始,后来才恢复到 Promise<T> - 它有效但并不有趣。你要的东西有一个名字。

检查异常

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

 public T foo() throws E {}

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

假设您有一个函数,该函数 return 是一个建立数据库连接以获取 URL 的承诺,发出 Web 请求并将其写入文件。 Java 中的 promise 相当于:

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

多次输入并没有什么乐趣——因此这些语言(如 C#)中的异常类型通常是隐式的。也就是说,如果您喜欢这种风格选择,那么您绝对应该这样做。这真的不是微不足道的——承诺的类型已经相当复杂了:

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

这就是 then 所做的 - 它接受类型 T 的承诺,以及接受 T 和 returns 值 (U) 或值承诺 (Promise) 的回调- return 是一个 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 个类型参数,这是人们通常想要避免的 :) 这完全有可能,但取决于你。

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

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

这里说的是Promise<T>.then()的return类型是Promise<U>,但是如果不指定onFulfill,那么return类型实际上是[=16] =]!

这甚至没有涉及 onFulfillonReject 都可以抛出的事实,给你五个不同的错误来源来协调以确定 [=19 的类型=]:

  • 如果 onReject 未指定,p 的拒绝值
  • onFulfill
  • 中抛出错误
  • onFulfill
  • 编辑的 return 承诺的拒绝值
  • onReject
  • 中抛出错误
  • onReject
  • 编辑的 return 承诺的拒绝值

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

如果我们拿同步代码来类比,一段代码的结果值是可以很好定义的。代码块可能抛出的错误集很少(除非,正如本杰明指出的那样,您正在编写 Java)。

进一步类比,即使使用 TypeScript 的强类型,它甚至不提供 (AFAIK) 指定捕获异常类型的机制,因此具有 any 错误类型的承诺与如何TypeScript 处理同步代码中的错误。

this page about that very matter 上的评论部分包含一条我认为与此处非常相关的评论:

By definition, an exception is an 'exceptional' condition, and could occur for a number of reason (e.g. syntax error, stack overflow, etc...). And while most of these errors do derive from the Error type, it is also possible something you call into could throw anything.

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

Since we don't have any notion of what exceptions a function might throw, allowing a type annotation on 'catch' variable would be highly misleading - it's not an exception filter and it's not anything resembling a guarantee of type safety.

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


还要注意:许多包含类似 promise 结构的强类型语言也没有做任何事情来表达它们可能产生的潜在错误的类型。 .NET 的 Task<T> 和 Scala 的 Future[T] 一样,结果只有一个类型参数。 Scala 封装错误的机制 Try[T](它是 Future[T] 接口的一部分)除了继承自 Throwable 之外,不对其结果错误类型提供任何保证。所以我想说 TypeScript 中的单一类型参数承诺很好。