Typescript typing for the `reason` error in various implementations of Promises?

Existing d.ts definition d.ts for various promise libraries seem to be d.ts data type provided for failure callbacks.

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; } 

qdts:

 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 (prom-ish):

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

I see nothing in the Promises / A + spec , suggesting that reason cannot be printed.

I tried to execute it on qdts, but the type information seems to be lost when the transition from 'T to 'U occurs, and I don’t quite understand why this should be so - and my attempts (mechanically adding the 'N and 'F parameters to the parameters <T> and 'O and 'G for <U> and type things as I thought they should be), mainly in {} , which is the type for the newly added parameters.

Is there a reason why they cannot be provided with their own type parameter? Is there a promises construct that can be fully typed?

+5
source share
2 answers

I think one of the main problems in achieving something like this is that then parameters are optional, and its return type depends on whether they are functions or not. Qdts does not work correctly, even with one type parameter:

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

This suggests that the return type of Promise<T>.then() is Promise<U> , but if onFulfill not specified, the return type is actually Promise<T> !

And this is not even due to the fact that onFulfill and onReject can throw onReject , giving you five different sources of errors for consistency to determine the type of p.then(onFulfill, onReject) :

  • Deviation p if no onReject specified
  • Error in onFulfill
  • onFulfill promise rejection value
  • Error in onReject
  • The promise reject value returned by onReject

I am sure that there is not even a way to express bullets 2 and 4 in TypeScript, since it did not check for exceptions.

If we take the analogy with synchronous code, the resulting value of the code block can be clearly defined. A possible set of errors created by a block of code is rare (if, as Benjamin points out, you write in Java).

To continue this analogy, even with strong TypeScript typing, it does not even provide (AFAIK) a mechanism for specifying types on detected exceptions, so promises with an error type of any are consistent with how TypeScript handles errors in synchronous code.

The comment section on this page about this actually contains a comment, which I think is very appropriate:

By definition, an exception is an “exceptional” condition and can occur for a number of reasons (for example, syntax error, stack overflow, etc.). And although most of these errors come from the type of error, it is also possible that something you cause can throw something.

The reason given on the same page for non-compliance with typed exceptions is also very relevant:

Since we don’t have a clue about what kind of exceptions a function can cause to annotate a type in a catch variable, this is not an exception filter, and it doesn’t look like a type guarantee.

Therefore, my advice is not to try to bind the error type to the definition of your type. Exceptions are unpredictable by their very nature, and a typed definition of .then already quite difficult to determine as it is.


It should also be noted: many strongly typed languages ​​that contain an unlike structure also do not help express the type of possible errors that they can produce..NET Task<T> has a single parameter for the result, like Scala Future[T] . Scala's mechanism for encapsulating errors, Try[T] (which is part of the Future[T] interface) gives no guarantees for its resulting error type, other than inheriting from Throwable . Therefore, I would say that the same type of promises parameter in TypeScript is in good company.

+3
source

Yes, it is really hard. This is a really good question that you are asking here.

Let me start with

Yes, it can be printed

It is quite possible to create a type of promise that takes into account exceptions. When I implemented the promise library in a typed language, I started with the type Promise<T,E> and only later returned to Promise<T> - it worked, but it was not fun. You have a name.

Checked Exceptions

What you are actually asking for here are the exceptions that need to be checked - this is a function that should declare the type of exceptions that it can throw - there are languages ​​that really do this for exceptions ... there is a language that does it - Java . In Java, when you have a method that can throw an exception (except for RuntimeException), it should declare it:

  public T foo() throws E {} 

See in Java - both the return type and the error type are part of the method signature. This is a controversial choice, and many people find it tedious. This is very unpopular among developers of other languages ​​because it forces you to write a lot of cool code.

Imagine you have a function that returns a promise that makes a db connection to get the url, makes a web request and writes it to a file. The promise equivalent to what you have in Java is something like this:

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

It's not very fun to type many times, so the types of exceptions in these languages ​​(like C #) are usually implicit. If you like this stylistic choice, you should definitely do it. This is just not entirely trivial: the type of promise is already quite complex:

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

This is what then does — it requires a promise of type T and a callback that takes T and returns a value (U) or a promise for a value (Promise) - and returns Promise (U-turn and transform). The actual type is even more complex since it has a second rejection argument - and both arguments are optional:

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

If you add error handling, it will become really “funny”, since all these steps now have an additional error path:

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

In principle - then now has 4 types of parameters that people usually want to avoid :) This is quite possible, although it’s up to you.

+5
source

Source: https://habr.com/ru/post/1216216/


All Articles