errors on await on non-promise values, await expecting then()
I'd like an option (e.g. strictAwait) that causes await on non-promise expressions to be an error.
Awaiting values of union types where one option is a promise probably should still be ok, so you can await on something that could be a promise or a value.
The following conditional type would reflect this behavior AFAIK:
```typescript
type MayBePromise
`await` would then only be allowed on values of type `T` where `MayBePromise<T>` is equal to `true`.
## Use Cases
While it's not a direct runtime error to `await` a non-promise value, it's still most likely a bug, because you expected something to be a promise, but it isn't.
The most common case for me is a missing call to `toPromise()` on an observable I just want to activate without caring about the result. This is an example using apollo-client, where the `mutate` method returns a cold observable that you need to subscribe to in order to actually do something:
```typescript
class Service {
constructor(private apollo: Apollo) {}
async doSomething() {
await this.apollo.mutate({ /* ... */ }).toPromise();
}
}
If you omit the toPromise(), everything still typechecks, but nothing will happen.
Calling await on immediate values won't do any harm (apart from performance probably), but it's still misleading, and it will be a hard-to-find bug when a then method will be added to the value in the future.
This should still compile:
const promiseA: Promise<string> = undefined as any;
const a: string = await promiseA;
const promiseB: Promise<string>| string = undefined as any;
const a: string = await promiseA;
// result type of `await` does not change, it still merges promise and non-promise types
const promiseC: Promise<string>| number = undefined as any;
const C: string | number = await promiseA;
This should not compile:
const nonPromise: number = undefined as any;
const err: number = await nonPromise;
My suggestion meets these guidelines:
It's perfectly valid in JavaScript to await on a non-promise expression:
If the value of the expression following the await operator is not a Promise, it's converted to a resolved Promise.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await
If you really want to prevent this, then a linter seems much more appropriate.
It's perfectly valid in JavaScript to await on a non-promise expression:
I know, that's why I explained my reasining in the use case section. I think this is similar to checking the thruthyness of a function value - that's also perfectly valid, but most likely a bug. I'm glad TypeScript now reports this an an error.
@typescript-eslint has the rule await-thenable that disallows awaiting non-promise value.
@kzok Thanks, didn't know this existed. Indeed, a linter seems to be appropiate for this.
This is intentional; await x when x is e.g. string | Promise<string> is a good ™ idiom. await on a non-Promise may also be used intentionally to allow an event loop tick to proceed.
@RyanCavanaugh, could this strict mode simply not error on the former case where a type is possibly a Promise (eg., string | Promise<string>)?
As for the latter case, could it not be handled by either leaving the mode disabled, or using // @ts-ignore to disable the specific error?
Using // @ts-ignore should not be the way to deal with valid type-safe code. It's not an error.
This seems to be a job for a linter, but not for the compiler.
This seems to be a job for a linter, but not for the compiler.
Counterpoint: Couldn't the same be said for uncalled function checks?
To lay bare my personal motivations, we currently use the await-promise TSLint rule for this exact case. However, it is the only rule we use that makes use of type information, and our >1M lines-of-code project takes several minutes to initialize a TS program for TSLint. If this were folded into Typescript, then we won't need to initialize a TS program twice.
Most helpful comment
@typescript-eslinthas the ruleawait-thenablethat disallows awaiting non-promise value.