-
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
Suggestion: Implied Promise return type in async functions #7284
Comments
Would you suggest doing the same for generator functions for consistency: function* foo() { // inferred return type is `IterableIterator<number>`
yield 1; // change that to just `number` under this proposal?
yield 2;
yield 3;
} Also, I think a case could be made for this change being equally confusing, even as you say when the function foo(): string {/***/}
var s1: string = foo(); // OK
async function bar(): string {/***/}
var s2: string = bar(); // ERROR |
I think that would make sense; though I haven't wrapped my head around generators enough to be confident.
That's a good error to have! Lets you know that you should be |
I also think it is annoying to have to specify the |
Then how could we specify that we want to use Bluebird's |
As of #6631, you can't do that any more (except by providing your own |
@tinganho the whole |
@yortus sorry my bad, i was referring to the original proposal that |
I think a fuller example showing the pain point / motivating scenario would be useful. The OP gave this example: async function makeStuff():Stuff {} But it's unclear from this line why the And on the point of return type inference, @nevir what do you propose the language service should do with this? E.g. if in VS Code I hover over the |
It is good practice to specify the intent first and then implement the body. If you intend the type to be of some type And in this case my intention of a function is to be async and have a return type of |
Interesting.... I agree with this statement but draw a rather different conclusion. I like to express my intent through clear interfaces that can be publicly shared with callers, and then provide an implementation that satisfies these interfaces without callers needing to know the implementation details. In this light, using an async function is often an implementation detail - it's one of several possible ways to implement a function that returns a promise. For example, I may provide some public API function that returns a 'thenable', without wanting to couple callers to the specific promise implementation, which might be export class Foo {
makeStuff(): Thenable<Stuff>;
} Now I can implement this class in a variety of ways that satisfy the contract, keeping the implementation details decoupled from the public interface. One such way would be like this: export class Foo {
async makeStuff(): Thenable<Stuff> {
// make some stuff...
// returns an ES6 Promise of stuff, which is-a Thenable<Stuff>
}
} Another way would be like this: export class Foo {
makeStuff(): Thenable<Stuff> {
return new bluebird.Promise(...)
}
} Either way, the public interface remains the same. The declaration |
@nevir I think your proposal misses how interface declarations should look like but I guess they should use the async keyword as well? interface A {
async method(): string;
} |
How is this: interface A {
async method(): string;
} different from this: interface A {
method(): Promise<string>;
} Does the first one mean that the interface only matches an |
@yortus my thought was that there is no difference between them. |
Actually, that's not a valid signature - |
I think this proposal is to make it valid. Otherwise it is a little bit inconsistent that you can write async with implied |
The compiler can infer the type. However, there are times when I am in favor of explicit typings:
Ideally, I would think that you'd want to display
Ah, yeah, I didn't think about interfaces, but yes, that would be ideal. |
To be clear: My main proposal is inferring the return type when implementing an Supporting it in other situations (interfaces, the language service, etc) is ideal - but I would assume a large undertaking? The motivation behind this request is:
|
Since Promises can't nest (i.e. you can't have a |
@nevir do you have a motivating example with code? |
I don't have any I can share, unfortunately. But here's an example from around the community:
In both cases, declaring the type instead of letting it be inferred provides extra security. Both make calls to other functions w/ similar contracts, and you would want to know if they modify their signature (say during a refactor, etc) |
If this proposal was accepted, it would be impossible to declare an async method as returning Fundamentally, this is lying about the return type of the function, which can clash with more complex type declarations. I don't think that saving nine characters
That is not a good argument because it is not true. |
It is my understanding that an At least, that's how I'm interpreting section 1.1.8 of the I.e. async function foo() { return 123; }
const result = foo(); // This is a Promise that resolves to 123. |
I understand that, but it's not something that TypeScript can control. If you want to change that behavior, you really should be bringing it up with TC39, as it will affect all JavaScript based platforms, not just TypeScript. At some point, the various vendors will implement this natively (and probably will start doing so relatively soon, since it's a stage 3 proposal). IMO It would be disastrous for TypeScript's The |
@nevir I think you did not get it right. It would be good to be able to say that an async method returns a union type that includes a promise. For example |
@nevir just to be clear, @jods4 is fully aware that async functions always return a promise and is not suggesting otherwise. There is a clear distinction between the actual return type and the return type annotation, the latter being completely in TypeScript's control. In TypeScript, return type annotations just have to be assignment-compatible. E.g., His example shows a practical benefit of this distinction, where a base class method is always async, but allows derived classes to override this method with a sync implementation, and it all passes type-checks. Here is a practical example. Suppose we maintain a library that uses promises in it's public API. But we don't want our library users to depend on the specific Promises/A+ implementation we use to implement the library, so that we can change the implementation later, say from bluebird promises to native async/await, or so that we can support polyfilling in browsers, etc. If our API specifically exposes say bluebird promises or es6 promises, then our users may start So instead, we write a generic interface import * as BluebirdPromise from 'bluebird';
export interface Thenable<R> {
then<U>(onFulfilled?: (value: R) => U | Thenable<U>, onRejected?: (error: any) => U | Thenable<U>): Thenable<U>;
then<U>(onFulfilled?: (value: R) => U | Thenable<U>, onRejected?: (error: any) => void): Thenable<U>;
catch<U>(onRejected?: (error: any) => U | Thenable<U>): Thenable<U>;
}
export function getFoo(): Thenable<Foo> {
// returns a BluebirdPromise...
return new BluebirdPromise((resolve, reject) => {...});
}
export function getBar(): Thenable<Bar> {
// returns a BluebirdPromise...
return getFoo().then(...).then(...);
} Problem solved! Now our library users just see Later, let's say we want to update The obvious approach would be: export async getBar(): Thenable<Bar> {
let foo = await getFoo();
...
return bar;
} This implementation clearly always returns an es6 promise, but our library users see the same And that's where we get to current TypeScript and this proposal:
|
Discussed and basically came to the same conclusions as @jods4 and @yortus. Apparently the people working on async thought of doing this originally as well and decided it'd be a bad idea. There's also the fundamental tenet that a return type annotation shouldn't depend on modifiers on the other side of the screen to be interpreted correctly. |
Apologies if this has already been brought up, but I wasn't able to find any issues that cover this topic (#5911 seems closest).
The Current State
Currently, if you want to enforce typing of an
async
function's resolved value, you must declare a return type of:Promise<ResolveType>
:This is a bit counterintuitive when it comes to
async
functions, as they generally mask the fact that there's aPromise
there to begin with. If you are utilizing promises purely viaasync
/await
, you're never going to see the tokenPromise
in your code.Proposal
When providing the return type of an
async
function, implicitly wrap it in aPromise<T>
(unless it is an explicitPromise<T>
type expression?). This seems reasonable now that #6631 is in.Seems like the least surprising way of declaring an async function
The text was updated successfully, but these errors were encountered: