You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
if you use a type annotation, it is going to be used to create the promise object wrapping the return value. there is no "value" named PromiseLike, so it is going to fail at runtime.
so either do not give it a type annotation, and it would be inferred to be the built-in "Promise", or use a Promise library.
This might get complicated if you use two libraries which use different promise implementation. Then the code needs to wrap them.
I still think that the code should allow PromiseLike.The awaiter code the compiler generate could still create an instance of a Promise since it conforms to PromiseLike. However the cast method must not do an instanceof Promise check. Instead it would need to check for a then function. All the awaiter code needs right now is a then function.
Dirk Bäumer (@dbaeumer) if you want to annotate the return value as PromiseLike<T>, then just return a value assignable to type T from the async function and it works fine. E.g.:
EDIT: Ahhh, I think I see the error you are talking about now:
Type PromiseLike<...> is not a valid async function return type
I tend to agree - the compiler is being too strict here. PromiseLike<any> is a valid annotation for any async function return value, because whatever promise instance is returned is sure to be assignable to it.
Dirk Bäumer (@dbaeumer) but why do you need the line return Promise.resolve(undefined);? Why not just allow the async body to return - it will wrap the return value in a promise - you don't need to create the promise explicitly. i.e.:
asyncfunctiondelayer(millis: number){awaituse();}
That will return Promise<void>, no need for any annotations or creating explicit promises.
Troy Gerwien (@yortus) the example is not about implementing a delayer :-). Here is what I want to do: I have a library that provides APi to others. This API is speced in terms of PromiseLike. Now I want to asyncfy the library. To do so I need to change all signatures to return Promise instead of PromiseLike otherwise I can't use await inside the implementation. This is still doable. What I don't fully understand what will happen if I have code like this:
functionimported(): PromiseLike<string>{// returns a Promise that is not the 'native' Promise implementation in node.}asyncfunctionwrap(){returnimported();}asyncfunctionuse{varvalue=awaitwrap();console.log(value);}
Will this always print a string value or could it happen to be the Promise type returned by imported(). The awaiter code does some instanceof Promise checks. If it always prints the strings I think it would be more clear if async functions return a PromiseLike
Dirk Bäumer (@dbaeumer) if you do it like this it should always work regardless of promise implementation:
functionimported(): PromiseLike<string>{// returns a Promise that is not the 'native' Promise implementation in node.}asyncfunctionwrap(){returnawaitimported();// 'unwraps' the PromiseLike and 're-wraps' as native promise}asyncfunctionuse{varvalue=awaitwrap();console.log(value);}
Dirk Bäumer (@dbaeumer) to add to the above, the instanceof checks in the awaiter should only relate to return values inside async functions, so that if a Promise<T> is returned it does not get wrapped into a Promise<Promise<T>>.
But the await operator should work with any A+ thenable regardless of what its specific implementation is. So return await x works with any thenable x.
Troy Gerwien (@yortus) I know, but this might be error prone. And in C# I can always return the called async method without await which is very nice (and to my knowledge save some CPU cycles)
Dirk Bäumer (@dbaeumer) Awaiting an A+ promiselike won't be error prone. If you're worried about the performance difference between return x and return await x, benchmark it and see. Given that async functions are downleveled to ES6 generators which V8 can't optimise, I think you'll find any performance difference very minor next to the slowdown due to the generator body not being optimised.
Dirk Bäumer (@dbaeumer), Mohamed Hegazy (@mhegazy): Even if we changed the behavior for async functions to only use a "Promise" value that is in scope, we would still need to watch out for import elision. Consider:
Troy Gerwien (@yortus): If we were to drop the type-directed emit for the return-type annotation, there could be possible confusion for end users. Consider:
The above would still be legal, as we would check assignability of MyPromise<T> to Promise<T> as if MyPromise<T> were an interface, however:
console.log(f()instanceofMyPromise);// "false"
In C#, an async method must return Task, Task<T> or void. If we dropped the ability to support custom Promise types or Promise subclasses from an async function, then our only option would be to ensure that the return type must be Promise<T> to avoid possible confusion due to our structural type system. In that case, returning PromiseLike<T> would still not be legal.
If we wanted to be very strict about the return type, then we wouldn't even allow local redeclaration of Promise, and would only allow the use of a global Promise. I still contend that this would make it harder for developers to use async functions in a down-level host once we support down-level emit for generators.
I tend to agree with Troy Gerwien (@yortus) that the current behavior is confusing since type annotation have an impact on the generated JS code.
Dirk Bäumer (@dbaeumer) its not the confusion so much that bothers me, it's that it doesn't seem to reconcile with TypeScript's own design goals. I'm astounted that there hasn't been any direct response to this so far. The goals are clear - types should not change emitted code, and the type system should be fully erasable. The current implementation disregards both of these goals in its treatment of return type annotations. It would be nice at the very least for someone to acknowledge this. Maybe they are just general guidelines and not strict rules. That's fine - please just someone say something to the community about this.
We would still need to ensure Promise is not elided even though its only used in type position.
Ron Buckton (@rbuckton) good point, this also applies to custom promises and promise polyfills in the current implementation - just filed #6003 for this.
OK, this could be confusing for users who think that return type annotations affect runtime behaviour - which given that TypeScript's type system is supposed to be completely erasable should be nobody. But let's say it is confusing for argument's sake. Here is the same code slightly modified to use generator functions instead:
Shouldn't this be just as confusing by the same argument? If this is not confusing, isn't that because users know that return type annotations do not actually modify the generated code?
If we dropped the ability to support custom Promise types or Promise subclasses from an async function
I don't think anybody is suggesting dropping this support.
Anyway what you think of the suggestion here: #5911 (comment)? This would give:
...would compile and work - because PromiseLike<T> is only a type (ie exists in type but not value space), so this would be a normal async function (ie not using custom promise) and the return type annotation would just need to be compatible with Promise.
If we dropped the ability to support custom Promise types [...], our only option would be to ensure that the return type must be Promise<T> to avoid possible confusion due to our structural type system. In that case, returning PromiseLike<T> would still not be legal.
Here is this logic, as I understand it, expressed as a function:
It compiles just fine. The return value is a Promise<T> and the return type PromiseLike<T>is legal. So there shouldn't be any problem annotating PromiseLike<T> on an async function that returns a Promise<T>.
In other words, if we know the async function returns a Promise<T> (which was the premise of your comment) then we know PromiseLike<T> is a valid annotation because we know that Promise<T> is always assignable to PromiseLike<T>. Why would this not be legal?
Mohamed Hegazy (@mhegazy), Ron Buckton (@rbuckton) What do you think of the following minor change to async function parse/emit to address this issue? It's a non-breaking change and it makes type annotations on async functions consistent with how they behave for ordinary and generator functions.
Given this sample code
asyncfunctionfoo(): P{/***/}// or if no annotation, treat P as Promise<any>
Current compiler behaviour pseudocode, with proposed additional logic
let isPromiseType = P is assignable to PromiseLike<any>
let isPromiseConstructor = typeof P is assignable to PromiseConstructorLike
if (isPromiseType) {
if (isPromiseConstructor) {
// use P as custom promise type in emit
emit async function with `P` as third argument to `__awaiter`
}
// <========== PROPOSED ADDITIONAL LOGIC ==========>
else if (target >= ES6) {
// P is not a constructable promise, but it is a promise-like type
// use builtin Promise class in emit
emit async function with `Promise` as third argument to `__awaiter`
}
// </========== PROPOSED ADDITIONAL LOGIC ==========>
else {
generate compiler error `"Type 'P' is not a valid async function return type"`
}
}
else {
generate compiler error `"Type 'P' is not a valid async function return type"`
}
Results:
classCustomPromiseextendsPromise<any>{}classFooBar{}interfaceThenable{then(onfulfilled?: (value)=>any,onrejected?: (reason)=>any): Thenable;}asyncfunctionf1(){}// OK, returns a builtin Promiseasyncfunctionf2(): CustomPromise{}// OK, returns a CustomPromiseasyncfunctionf3(): FooBar{}// ERROR: 'FooBar' is not a valid async function return typeasyncfunctionf4(): Thenable{}// OK, return type is Thenable, returns a builtin Promiseasyncfunctionf5(): any{}// OK, return type is any, returns a builtin Promiseasyncfunctionf6(): number{}// ERROR: number is not a valid async function return type
Note f4 and f5 are currently errors but would be valid with this proposed change.
// P is not a constructable promise, but it is a promise-like type
Troy Gerwien (@yortus), the proposal above is actually a type-directed emit. The emit logic, in this proposal, depends on whether the type checker managed to correctly resolve the type from the type annotation, and correctly identified that it is constratable.
This breaks the transpile scenarios, where the compiler has access only to one file at a time. For these scenarios the transformation done by the emitter is expected to be a pure syntactic transformation on a file-level with no involvement of the type system what so ever. .d.ts files are not even loaded, even lib.d.ts is not loaded.
You have referred to the current implementation as "type-directed" emit. That is not correct. The current implementation will always emit the same output regardless of the return type, whether it is defined or not, whether the type checker resolved it correctly or not, or whether it is a single file transformation or a whole program compilation. This is one of the axioms that we have strived to maintain and have rejected a lot of proposals that will contradict it.
Just to elaborate, type-directed emit means that the emitted code for a given input is based on the view of the type system. if the type system thinks of a name as a number, it will emit it differently from if it thought it was a string.
The async function transformation has nothing to do with the type system, it is purely syntactic. it is however, type-annotation specific emit. for that, i do not see the problem, and i do not see how that is significantly different from using the value "Promise" from the current scope.
This breaks the transpile scenarios, where the compiler has access only to one file at a time.
This finally might explain something about why we seem to be at such cross purposes. Can you please confirm if I have understood the implications of this. Suppose the compiler needs to emit code for async (): M => {} but doesn't know what type M is. The current implementation always emits M, leaving the question of validity of M in that type position to the checker. OTOH what I have proposed above needs to emit M if M is a constructable promise type, but if M is just an interface it needs to emit Promise instead. But it doesn't have information about M's type so it can't know what to emit.
Is that what you mean?
This may clear up a few things. I've been looking from an 'in principle' perspective. I.e. in principle, PromiseLike<T> is a valid type annotation for any function returning a Promise<T>, regardless whether it's (): PromiseLike<any> => Promise.resolve(42) or async (): PromiseLike<any> => 42. I mean in principle. There exists a compiler implementation that can do this. It is consistent with type theory.
OTOH I think you are speaking from an implementation perspective. The way the TypeScript compiler is structured, and which parts have access to which information at which time, and what compiler options must be supported (eg single file parse/emit) all constrain the possibilities in practice.
Would that be a fair assessment? I hope so because it has been very frustrating (I'm sure for you too) trying to understand the objections to supporting syntax like async (): PromiseLike<any> => 42, and why function* (): any {} is valid but async function (): any {} is an error.
The current implementation will always emit the same output regardless of the return type
If you mean the return type annotation, what about #6007?
You have referred to the current implementation as "type-directed" emit. That is not correct.
We unfortunately cannot loosen the restriction around the return type annotation of an async function, as there may be existing user code that relies on the return type annotation pointing to a type that also has a reachable constructor value of the same name.
As of #6631 we are further restricting the return type annotation of an async function in ES6 or higher to be onlyPromise<T>. The rationale for that decision can be found here: #6631 (comment).
I am closing this issue based on our current plans to restrict the return type annotation to only Promise<T>. We may reinvestigate this issue in a later release.
Reacted by Morten Christensen, Liberty Rowland and Matti Järvinen
I've read through a bunch of these issues, and I have to raise this flag again.
Just now I installed 2.2 dev and I'm getting this error. I understand the concerns about how specifying the actual promise that ends up getting used is a good choice. I also understand how the awaiter works.
I have my own Promise lib. And I still don't see the value in making this more restrictive.
The PromiseLike interface is... Well something should be available that is more generic/broad IMO.
Activity
mhegazy commentedon Dec 3, 2015
if you use a type annotation, it is going to be used to create the promise object wrapping the return value. there is no "value" named PromiseLike, so it is going to fail at runtime.
so either do not give it a type annotation, and it would be inferred to be the built-in "Promise", or use a Promise library.
Arnavion commentedon Dec 4, 2015
Also, since you're returning a promise and not the value, you don't need to mark it as
asyncanyway.dbaeumer commentedon Dec 4, 2015
This might get complicated if you use two libraries which use different promise implementation. Then the code needs to wrap them.
I still think that the code should allow PromiseLike.The awaiter code the compiler generate could still create an instance of a Promise since it conforms to PromiseLike. However the cast method must not do an instanceof Promise check. Instead it would need to check for a then function. All the awaiter code needs right now is a then function.
yortus commentedon Dec 4, 2015
Dirk Bäumer (@dbaeumer) if you want to annotate the return value as
PromiseLike<T>, then just return a value assignable to typeTfrom theasyncfunction and it works fine. E.g.:EDIT: Ahhh, I think I see the error you are talking about now:
I tend to agree - the compiler is being too strict here.
PromiseLike<any>is a valid annotation for any async function return value, because whatever promise instance is returned is sure to be assignable to it.dbaeumer commentedon Dec 4, 2015
Arnav Singh (@Arnavion) shorten my example to much. I need the async keyword since I want to use await in the body. Something like:
yortus commentedon Dec 4, 2015
Dirk Bäumer (@dbaeumer) but why do you need the line
return Promise.resolve(undefined);? Why not just allow the async body toreturn- it will wrap the return value in a promise - you don't need to create the promise explicitly. i.e.:That will return
Promise<void>, no need for any annotations or creating explicit promises.yortus commentedon Dec 4, 2015
Is this what you are trying to do:
That will return a
Promise<void>that resolves aftermillismilliseconds, and the resolution value will beundefined.dbaeumer commentedon Dec 4, 2015
Troy Gerwien (@yortus) the example is not about implementing a delayer :-). Here is what I want to do: I have a library that provides APi to others. This API is speced in terms of PromiseLike. Now I want to asyncfy the library. To do so I need to change all signatures to return Promise instead of PromiseLike otherwise I can't use await inside the implementation. This is still doable. What I don't fully understand what will happen if I have code like this:
Will this always print a string value or could it happen to be the Promise type returned by imported(). The awaiter code does some instanceof Promise checks. If it always prints the strings I think it would be more clear if async functions return a PromiseLike
I tested it with the Promise implementation form here https://github.com/then/promise and it returned a string.
yortus commentedon Dec 4, 2015
Dirk Bäumer (@dbaeumer) if you do it like this it should always work regardless of promise implementation:
yortus commentedon Dec 4, 2015
Dirk Bäumer (@dbaeumer) to add to the above, the
instanceofchecks in the awaiter should only relate toreturnvalues insideasyncfunctions, so that if aPromise<T>is returned it does not get wrapped into aPromise<Promise<T>>.But the
awaitoperator should work with any A+ thenable regardless of what its specific implementation is. Soreturn await xworks with any thenablex.dbaeumer commentedon Dec 4, 2015
Troy Gerwien (@yortus) I know, but this might be error prone. And in C# I can always return the called async method without await which is very nice (and to my knowledge save some CPU cycles)
jrieken commentedon Dec 4, 2015
Dirk Bäumer (@dbaeumer) Isn't it possible to just await a function returning a promise (no async marker)?
yortus commentedon Dec 4, 2015
Dirk Bäumer (@dbaeumer) Awaiting an A+ promiselike won't be error prone. If you're worried about the performance difference between
return xandreturn await x, benchmark it and see. Given thatasyncfunctions are downleveled to ES6 generators which V8 can't optimise, I think you'll find any performance difference very minor next to the slowdown due to the generator body not being optimised.dbaeumer commentedon Dec 4, 2015
Troy Gerwien (@yortus) what I meant with error prone is that people might forget it (especially if they come from a C# background).
40 remaining items
rbuckton commentedon Dec 8, 2015
Dirk Bäumer (@dbaeumer), Mohamed Hegazy (@mhegazy): Even if we changed the behavior for async functions to only use a "Promise" value that is in scope, we would still need to watch out for import elision. Consider:
We would still need to ensure
Promiseis not elided even though its only used in type position.rbuckton commentedon Dec 8, 2015
Troy Gerwien (@yortus): If we were to drop the type-directed emit for the return-type annotation, there could be possible confusion for end users. Consider:
The above would still be legal, as we would check assignability of
MyPromise<T>toPromise<T>as ifMyPromise<T>were an interface, however:In C#, an async method must return
Task,Task<T>orvoid. If we dropped the ability to support custom Promise types or Promise subclasses from an async function, then our only option would be to ensure that the return type must bePromise<T>to avoid possible confusion due to our structural type system. In that case, returningPromiseLike<T>would still not be legal.If we wanted to be very strict about the return type, then we wouldn't even allow local redeclaration of
Promise, and would only allow the use of a globalPromise. I still contend that this would make it harder for developers to use async functions in a down-level host once we support down-level emit for generators.yortus commentedon Dec 9, 2015
Mohamed Hegazy (@mhegazy) yes right, for ES3/ES5 it would be better to error since there is no builtin 'Promise'.
hardly a ringing endorsement :) But I really hope this can be done for consistency's sake.
yortus commentedon Dec 9, 2015
Dirk Bäumer (@dbaeumer) its not the confusion so much that bothers me, it's that it doesn't seem to reconcile with TypeScript's own design goals. I'm astounted that there hasn't been any direct response to this so far. The goals are clear - types should not change emitted code, and the type system should be fully erasable. The current implementation disregards both of these goals in its treatment of return type annotations. It would be nice at the very least for someone to acknowledge this. Maybe they are just general guidelines and not strict rules. That's fine - please just someone say something to the community about this.
yortus commentedon Dec 9, 2015
Ron Buckton (@rbuckton) good point, this also applies to custom promises and promise polyfills in the current implementation - just filed #6003 for this.
yortus commentedon Dec 9, 2015
Ron Buckton (@rbuckton) you say users would be confused by this:
OK, this could be confusing for users who think that return type annotations affect runtime behaviour - which given that TypeScript's type system is supposed to be completely erasable should be nobody. But let's say it is confusing for argument's sake. Here is the same code slightly modified to use generator functions instead:
Shouldn't this be just as confusing by the same argument? If this is not confusing, isn't that because users know that return type annotations do not actually modify the generated code?
I don't think anybody is suggesting dropping this support.
Anyway what you think of the suggestion here: #5911 (comment)? This would give:
...because
MyPromiseis a class (ie exists in both type and value spaces) - so that would be handled exactly as it is currently. Then this example:...would compile and work - because
PromiseLike<T>is only a type (ie exists in type but not value space), so this would be a normal async function (ie not using custom promise) and the return type annotation would just need to be compatible withPromise.Here is this logic, as I understand it, expressed as a function:
It compiles just fine. The return value is a
Promise<T>and the return typePromiseLike<T>is legal. So there shouldn't be any problem annotatingPromiseLike<T>on an async function that returns aPromise<T>.In other words, if we know the async function returns a
Promise<T>(which was the premise of your comment) then we knowPromiseLike<T>is a valid annotation because we know thatPromise<T>is always assignable toPromiseLike<T>. Why would this not be legal?yortus commentedon Dec 10, 2015
Mohamed Hegazy (@mhegazy), Ron Buckton (@rbuckton) What do you think of the following minor change to async function parse/emit to address this issue? It's a non-breaking change and it makes type annotations on async functions consistent with how they behave for ordinary and generator functions.
Given this sample code
Current compiler behaviour pseudocode, with proposed additional logic
Results:
Note
f4andf5are currently errors but would be valid with this proposed change.mhegazy commentedon Dec 10, 2015
Troy Gerwien (@yortus), the proposal above is actually a type-directed emit. The emit logic, in this proposal, depends on whether the type checker managed to correctly resolve the type from the type annotation, and correctly identified that it is constratable.
This breaks the transpile scenarios, where the compiler has access only to one file at a time. For these scenarios the transformation done by the emitter is expected to be a pure syntactic transformation on a file-level with no involvement of the type system what so ever. .d.ts files are not even loaded, even lib.d.ts is not loaded.
You have referred to the current implementation as "type-directed" emit. That is not correct. The current implementation will always emit the same output regardless of the return type, whether it is defined or not, whether the type checker resolved it correctly or not, or whether it is a single file transformation or a whole program compilation. This is one of the axioms that we have strived to maintain and have rejected a lot of proposals that will contradict it.
Just to elaborate, type-directed emit means that the emitted code for a given input is based on the view of the type system. if the type system thinks of a name as a number, it will emit it differently from if it thought it was a string.
The async function transformation has nothing to do with the type system, it is purely syntactic. it is however, type-annotation specific emit. for that, i do not see the problem, and i do not see how that is significantly different from using the value "Promise" from the current scope.
yortus commentedon Dec 10, 2015
This finally might explain something about why we seem to be at such cross purposes. Can you please confirm if I have understood the implications of this. Suppose the compiler needs to emit code for
async (): M => {}but doesn't know what typeMis. The current implementation always emitsM, leaving the question of validity ofMin that type position to the checker. OTOH what I have proposed above needs to emitMifMis a constructable promise type, but ifMis just an interface it needs to emitPromiseinstead. But it doesn't have information aboutM's type so it can't know what to emit.Is that what you mean?
This may clear up a few things. I've been looking from an 'in principle' perspective. I.e. in principle,
PromiseLike<T>is a valid type annotation for any function returning aPromise<T>, regardless whether it's(): PromiseLike<any> => Promise.resolve(42)orasync (): PromiseLike<any> => 42. I mean in principle. There exists a compiler implementation that can do this. It is consistent with type theory.OTOH I think you are speaking from an implementation perspective. The way the TypeScript compiler is structured, and which parts have access to which information at which time, and what compiler options must be supported (eg single file parse/emit) all constrain the possibilities in practice.
Would that be a fair assessment? I hope so because it has been very frustrating (I'm sure for you too) trying to understand the objections to supporting syntax like
async (): PromiseLike<any> => 42, and whyfunction* (): any {}is valid butasync function (): any {}is an error.If you mean the return type annotation, what about #6007?
I just adopted that phrase because that's how Ron Buckton (@rbuckton) and Daniel Rosenwasser (@DanielRosenwasser) describe it here and here. Given #6007, I think it needs some special name because no other features emit different code when you remove a type annotation AFAIK.
TheLevenCreations commentedon Jan 23, 2016
+1
I met this issue in my scenario. My tsconfig is set to es6, module AMD(typescript 1.7.5) and my code is like this:
And it will generate:
TPromise is just like PromiseLike, it is another promise implementation which I grabbed from vscode(winjs).
So the error message is:
TPromise is undefined
In my case, I think it should generate something like (focus on TPromise_1.TPromise part)
rbuckton commentedon Jan 27, 2016
We unfortunately cannot loosen the restriction around the return type annotation of an async function, as there may be existing user code that relies on the return type annotation pointing to a type that also has a reachable constructor value of the same name.
As of #6631 we are further restricting the return type annotation of an async function in ES6 or higher to be only
Promise<T>. The rationale for that decision can be found here: #6631 (comment).rbuckton commentedon Jan 27, 2016
I am closing this issue based on our current plans to restrict the return type annotation to only
Promise<T>. We may reinvestigate this issue in a later release.electricessence commentedon Nov 19, 2016
I've read through a bunch of these issues, and I have to raise this flag again.
Just now I installed 2.2 dev and I'm getting this error. I understand the concerns about how specifying the actual promise that ends up getting used is a good choice. I also understand how the awaiter works.
I have my own Promise lib. And I still don't see the value in making this more restrictive.
The PromiseLike interface is... Well something should be available that is more generic/broad IMO.