-
Notifications
You must be signed in to change notification settings - Fork 13.1k
Adds 'awaited' type to better handle promise resolution and await #17077
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
Will need to remember to make an issue to remove the Bikeshed: Is there a reason it's |
|
Problem 1 may be among the many issues that could also be solved given #6606: interface UnwrapPromise {
<U>(v: PromiseLike<U>): UnwrapPromise(U); // try unwrapping further, just in case
<U>(v: U): U;
// ^ else case: can't unwrap, so no-op
};
// ^ function type used to unwrap promise types
interface Promise<T> {
then<TResult1 = T, TResult2 = never>(
onfulfilled?: ((value: T) => TResult1) | undefined | null,
onrejected?: ((reason: any) => TResult2) | undefined | null
): Promise<UnwrapPromise(TResult1) | UnwrapPromise(TResult2)>;
}Since this approach does not rely on I'm not so much expecting the approach to change for this current in-progress PR; I'm just under the impression that particular proposal has been somewhat under-appreciated given what it could address. Edit: this |
|
It'd be really nice if you could solve these problems by making the overall type system sufficiently expressive, rather than adding magic user-inaccessible types. |
|
I concur with @masaeedu. It's feels like a brute force/patchy solution that might become a considerable debt in the near future. |
|
@masaeedu, my intention here is to make the type system more expressive. This introduces a type-system parallel to the With this change, the type system would have a mechanism to reflect at design time the behavior of both the |
|
@rbuckton There is nothing in particular that is special about |
|
@masaeedu That's not what this proposes. It is not JavaScript promises do not let you have a Promise for a Promise. Any time a Promise is settled with another Promise, the first Promise adopts the state of the second promise. This effectively unwraps the second Promise's value. Strongly typed languages like C# do not have this concept. Consider By introducing Perhaps @weswigham is correct and we need to bikeshed the keyword. Maybe Also, while |
|
@masaeedu The real problem is that |
|
@rbuckton Sorry, If you allow for type-level functions as @tycho01 has shown, you can express recursive unwrapping of the |
|
@gcnew With type-level functions, we already have a powerful way to encode conditional logic or base-cases for pattern matching on types: overloaded functions. This is actually very close to how type families in Haskell work, so I feel it is good enough, but I guess whether or not we need some other mechanism of conditional matching on types is a debate for a different issue (probably #12424). |
My hunch is also that with Edit: to ensure lazy evaluation you can't actually abstract that pattern into that |
|
Open items from design meeting on 2017-08-04:
|
|
In the design meeting on 2017-08-25 we decided on the name |
|
I now got to the point of being able to try that Edit: guess we knew the solution, i.e. separately calculating return types for different union constituents, see #17471 (comment). I suppose these type calls would have use for that -- I should try for a bit. Edit 2: looks like iterating over the options as suggested there did it, addressing the issue raised here without special-casing |
|
if #21613 lands, that should probably address the issues raised here. |
|
@tycho01, we discussed this in our last design meeting and unfortunately there are still issues with higher-order assignability between generic |
|
With conditional types in now, we might want to rethink our approach here. closing the PR for house keeping purposes since it is unactionable at the moment, but leaving the branch for further investigations. |
|
We may still need to pursue this approach given the fact that conditional types cannot solve this. |
Following the discussion in microsoft/vscode#30216, I noted that the
Promisedefinition in VSCode was out of date. I created microsoft/vscode#30268 to address this and ran into a number of cases where I had to add explicit type annotations to work around limitations in the checker.As a result, I discovered two classes of issues with how we handle resolution for Promise-like types:
Promisechains created viathen().To that end, I am introducing two features into the compiler:
awaited Ttype operator, used to explicitly indicate the "awaited type" of a typefollowing the same type resolution behavior as the
awaitexpression and providing an explicitparallel in the type system for the runtime behavior of
await.AwaitedTypetype in the checker, used to defer resolution of the "awaited type" of a typevariable until it is instantiated as well as to better support type relationships.
Incorrect return types or errors with complex Promise chains
The
then()method onPromisecurrently has the following definition:Generally, this works well if the return statements in the function passed to
onfulfilledare simple:However, the following results in errors:
To resolve this, this PR adds a new
awaited Ttype operator that allows a developer to morereliably indicate the type that would be the result of the implicit unwrapping behavior inherent to both
Promiseresolution as well as theawaitexpression.This allows us to update our definition of
Promiseto the following:With
awaited T, regardless what type you specify forT, the type ofvaluein theonfulfilledcallback will be recursively unwrapped following the same mechanics ofawait(whichin turn follows the runtime behavior of the ECMAScript specification).
In addition, the much simpler return types of the
onfulfilledandonrejectedcallbacks allowfor more complex logic inside of the bodies of the callbacks. The complex work of unwrapping the
return types of the callbacks is then pushed into the return type of
then, where the awaitedtypes of
TResultandTResult2are resolved.As such, we can now infer the correct types for the above example:
Incorrect eager resolution of the "awaited type" for a type variable
This brings us to the second issue this PR addresses. The underlying implementation of
awaitinthe type checker already recursively resolves the "awaited type" of an expression. For example:
However, it can fall short in cases where generics are concerned:
The reason we get the wrong type in the second example is that we eagerly get the "awaited type"
of
x, which isT. We cannot further resolveTas it has not been instantiated, so thereturn type of
fbecomesPromise<T>. When we supply a nested promise type tof, we assignthe type
MyPromise<number>toTwhich then instantiates the return type off(a)asPromise<MyPromise<number>>!To address this, TypeScript will now internally have the concept of an
AwaitedTypewhich indicatesthe deferred resolution of the "awaited type" of a type variable. As a result, whenever you
awaitan expression whose type is a type variable, or whenever we encounter an
awaited Toperator for atype variable, we will internally allocate an
AwaitedTypefor that type variable. Once the typevariable is instantiated, the "awaited type" will be resolved.
With this change, the failing example above now has the correct type:
This also allows us to more accurately check type relationships between the "awaited types" of
two generics, following these rules:
Out of scope
One thing this PR does not cover is attempting to recursively unwrap the
Tof aPromise<T>whenexplicitly written as such by the user. Generally this is unnecessary as the
Twill beunwrapped when using
then()orcatch():Bear in mind that this means that though
p1in the example above has the typePromise<Promise<number>>,p2will have the typePromise<number>. It may seem counter-intuitive, but this is the correct behavior and mitigates the need to add syntax to unwrap
type parameters.
Definitions
then()method of a promise.The callback accepts a single
valueparameter which is the fulfilled value of the promise.valueparameter for the onfulfilled callback of a promise.Tis promise-like, the awaited type of the fulfillment type ofT; otherwise,T.