-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Adds 'awaited' type to better handle promise resolution and await #17077
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
Promise
definition 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:
Promise
chains created viathen()
.To that end, I am introducing two features into the compiler:
awaited T
type operator, used to explicitly indicate the "awaited type" of a typefollowing the same type resolution behavior as the
await
expression and providing an explicitparallel in the type system for the runtime behavior of
await
.AwaitedType
type 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 onPromise
currently has the following definition:Generally, this works well if the return statements in the function passed to
onfulfilled
are simple:However, the following results in errors:
To resolve this, this PR adds a new
awaited T
type operator that allows a developer to morereliably indicate the type that would be the result of the implicit unwrapping behavior inherent to both
Promise
resolution as well as theawait
expression.This allows us to update our definition of
Promise
to the following:With
awaited T
, regardless what type you specify forT
, the type ofvalue
in theonfulfilled
callback 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
onfulfilled
andonrejected
callbacks 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
TResult
andTResult2
are 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
await
inthe 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 resolveT
as it has not been instantiated, so thereturn type of
f
becomesPromise<T>
. When we supply a nested promise type tof
, we assignthe type
MyPromise<number>
toT
which then instantiates the return type off(a)
asPromise<MyPromise<number>>
!To address this, TypeScript will now internally have the concept of an
AwaitedType
which indicatesthe deferred resolution of the "awaited type" of a type variable. As a result, whenever you
await
an expression whose type is a type variable, or whenever we encounter an
awaited T
operator for atype variable, we will internally allocate an
AwaitedType
for 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
T
of aPromise<T>
whenexplicitly written as such by the user. Generally this is unnecessary as the
T
will beunwrapped when using
then()
orcatch()
:Bear in mind that this means that though
p1
in the example above has the typePromise<Promise<number>>
,p2
will 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
value
parameter which is the fulfilled value of the promise.value
parameter for the onfulfilled callback of a promise.T
is promise-like, the awaited type of the fulfillment type ofT
; otherwise,T
.