Skip to content

Use awaited type for await #45701

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

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
235 changes: 168 additions & 67 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/harness/fourslashInterfaceImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1079,6 +1079,7 @@ namespace FourSlashInterface {
typeEntry("PromiseConstructorLike"),
interfaceEntry("PromiseLike"),
interfaceEntry("Promise"),
typeEntry("Awaited"),
interfaceEntry("ArrayLike"),
typeEntry("Partial"),
typeEntry("Required"),
Expand Down
16 changes: 16 additions & 0 deletions src/lib/es2015.promise.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ interface PromiseConstructor {
*/
new <T>(executor: (resolve: (value: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void): Promise<T>;

/**
* Creates a Promise that is resolved with an array of results when all of the provided Promises
* resolve, or rejected when any Promise is rejected.
* @param values An array of Promises.
* @returns A new Promise.
*/
all<T extends readonly unknown[] | []>(values: T): Promise<{ -readonly [P in keyof T]: Awaited<T[P]> }>;

/**
* Creates a Promise that is resolved with an array of results when all of the provided Promises
* resolve, or rejected when any Promise is rejected.
Expand Down Expand Up @@ -95,6 +103,14 @@ interface PromiseConstructor {
// see: lib.es2015.iterable.d.ts
// all<T>(values: Iterable<T | PromiseLike<T>>): Promise<T[]>;

/**
* Creates a Promise that is resolved or rejected when any of the provided Promises are resolved
* or rejected.
* @param values An array of Promises.
* @returns A new Promise.
*/
race<T extends readonly unknown[] | []>(values: T): Promise<Awaited<T[number]>>;

/**
* Creates a Promise that is resolved or rejected when any of the provided Promises are resolved
* or rejected.
Expand Down
3 changes: 1 addition & 2 deletions src/lib/es2020.promise.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ interface PromiseConstructor {
* @param values An array of Promises.
* @returns A new Promise.
*/
allSettled<T extends readonly unknown[] | readonly [unknown]>(values: T):
Promise<{ -readonly [P in keyof T]: PromiseSettledResult<T[P] extends PromiseLike<infer U> ? U : T[P]> }>;
allSettled<T extends readonly unknown[] | []>(values: T): Promise<{ -readonly [P in keyof T]: PromiseSettledResult<Awaited<T[P]>> }>;

/**
* Creates a Promise that is resolved with an array of results when all
Expand Down
7 changes: 7 additions & 0 deletions src/lib/es2021.promise.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ declare var AggregateError: AggregateErrorConstructor;
* Represents the completion of an asynchronous operation
*/
interface PromiseConstructor {
/**
* The any function returns a promise that is fulfilled by the first given promise to be fulfilled, or rejected with an AggregateError containing an array of rejection reasons if all of the given promises are rejected. It resolves all elements of the passed iterable to promises as it runs this algorithm.
* @param values An array or iterable of Promises.
* @returns A new Promise.
*/
any<T extends readonly any[] | []>(values: T): Promise<Awaited<T[number]>>;

/**
* The any function returns a promise that is fulfilled by the first given promise to be fulfilled, or rejected with an AggregateError containing an array of rejection reasons if all of the given promises are rejected. It resolves all elements of the passed iterable to promises as it runs this algorithm.
* @param values An array or iterable of Promises.
Expand Down
13 changes: 13 additions & 0 deletions src/lib/es5.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1440,6 +1440,19 @@ interface Promise<T> {
catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): Promise<T | TResult>;
}

/**
* Recursively unwraps the "awaited type" of a type. Non-promise "thenables" should resolve to `never`. This emulates the behavior of `await`.
*/
type Awaited<T> =
T extends null | undefined ? T : // special case for `null | undefined` when not in `--noImplicitAny` mode
T extends object ? // `await` only unwraps object types with a callable then. Non-object types are not unwrapped.
T extends { then(onfulfilled: infer F): any } ? // thenable, extracts the first argument to `then()`
F extends ((value: infer V) => any) ? // if the argument to `then` is callable, extracts the argument
Awaited<V> : // recursively unwrap the value
never : // the argument to `then` was not callable.
T : // argument was not an object
T; // non-thenable

interface ArrayLike<T> {
readonly length: number;
readonly [n: number]: T;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ tests/cases/conformance/async/es2017/asyncArrowFunction/asyncArrowFunction9_es20
!!! error TS1005: ',' expected.
~~~~~~~
!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'Promise' must be of type 'PromiseConstructor', but here has type 'any'.
!!! related TS6203 /.ts/lib.es2015.promise.d.ts:150:13: 'Promise' was also declared here.
!!! related TS6203 /.ts/lib.es2015.promise.d.ts:166:13: 'Promise' was also declared here.
~
!!! error TS1005: ',' expected.
~~
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ tests/cases/conformance/async/es5/asyncArrowFunction/asyncArrowFunction9_es5.ts(
!!! error TS1005: ',' expected.
~~~~~~~
!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'Promise' must be of type 'PromiseConstructor', but here has type 'any'.
!!! related TS6203 /.ts/lib.es2015.promise.d.ts:150:13: 'Promise' was also declared here.
!!! related TS6203 /.ts/lib.es2015.promise.d.ts:166:13: 'Promise' was also declared here.
~
!!! error TS1005: ',' expected.
~~
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ tests/cases/conformance/async/es6/asyncArrowFunction/asyncArrowFunction9_es6.ts(
!!! error TS1005: ',' expected.
~~~~~~~
!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'Promise' must be of type 'PromiseConstructor', but here has type 'any'.
!!! related TS6203 /.ts/lib.es2015.promise.d.ts:150:13: 'Promise' was also declared here.
!!! related TS6203 /.ts/lib.es2015.promise.d.ts:166:13: 'Promise' was also declared here.
~
!!! error TS1005: ',' expected.
~~
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ class C {
>method : () => void

var fn = async () => await this;
>fn : () => Promise<this>
>async () => await this : () => Promise<this>
>await this : this
>fn : () => Promise<Awaited<this>>
>async () => await this : () => Promise<Awaited<this>>
>await this : Awaited<this>
>this : this
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ class C {
>method : () => void

var fn = async () => await this;
>fn : () => Promise<this>
>async () => await this : () => Promise<this>
>await this : this
>fn : () => Promise<Awaited<this>>
>async () => await this : () => Promise<Awaited<this>>
>await this : Awaited<this>
>this : this
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ class C {
>method : () => void

var fn = async () => await this;
>fn : () => Promise<this>
>async () => await this : () => Promise<this>
>await this : this
>fn : () => Promise<Awaited<this>>
>async () => await this : () => Promise<Awaited<this>>
>await this : Awaited<this>
>this : this
}
}
Expand Down
52 changes: 52 additions & 0 deletions tests/baselines/reference/awaitedType.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
tests/cases/compiler/awaitedType.ts(18,12): error TS2589: Type instantiation is excessively deep and possibly infinite.
tests/cases/compiler/awaitedType.ts(22,12): error TS2589: Type instantiation is excessively deep and possibly infinite.


==== tests/cases/compiler/awaitedType.ts (2 errors) ====
type T1 = Awaited<number>;
type T2 = Awaited<Promise<number>>;
type T3 = Awaited<number | Promise<number>>;
type T4 = Awaited<number | Promise<string>>;
type T5 = Awaited<{ then: number }>;
type T6 = Awaited<{ then(): void }>; // never (non-promise "thenable")
type T7 = Awaited<{ then(x: number): void }>; // never (non-promise "thenable")
type T8 = Awaited<{ then(x: () => void): void }>; // unknown
type T9 = Awaited<any>;
type T10 = Awaited<never>;
type T11 = Awaited<unknown>;
type T12 = Awaited<Promise<Promise<number>>>;
type T13 = _Expect<Awaited<Promise<Promise<number>> | string | null>, /*expected*/ string | number | null>; // otherwise just prints T13 in types tests, which isn't very helpful
type T14 = _Expect<Awaited<Promise<Promise<number>> | string | undefined>, /*expected*/ string | number | undefined>; // otherwise just prints T14 in types tests, which isn't very helpful
type T15 = _Expect<Awaited<Promise<Promise<number>> | string | null | undefined>, /*expected*/ string | number | null | undefined>; // otherwise just prints T15 in types tests, which isn't very helpful

interface BadPromise { then(cb: (value: BadPromise) => void): void; }
type T16 = Awaited<BadPromise>; // error
~~~~~~~~~~~~~~~~~~~
!!! error TS2589: Type instantiation is excessively deep and possibly infinite.

interface BadPromise1 { then(cb: (value: BadPromise2) => void): void; }
interface BadPromise2 { then(cb: (value: BadPromise1) => void): void; }
type T17 = Awaited<BadPromise1>; // error
~~~~~~~~~~~~~~~~~~~~
!!! error TS2589: Type instantiation is excessively deep and possibly infinite.

// https://github.com/microsoft/TypeScript/issues/33562
type MaybePromise<T> = T | Promise<T> | PromiseLike<T>
declare function MaybePromise<T>(value: T): MaybePromise<T>;

async function main() {
let aaa: number;
let bbb: string;
[
aaa,
bbb,
] = await Promise.all([
MaybePromise(1),
MaybePromise('2'),
MaybePromise(true),
])
}

// helps with tests where '.types' just prints out the type alias name
type _Expect<TActual extends TExpected, TExpected> = TActual;

58 changes: 58 additions & 0 deletions tests/baselines/reference/awaitedType.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//// [awaitedType.ts]
type T1 = Awaited<number>;
type T2 = Awaited<Promise<number>>;
type T3 = Awaited<number | Promise<number>>;
type T4 = Awaited<number | Promise<string>>;
type T5 = Awaited<{ then: number }>;
type T6 = Awaited<{ then(): void }>; // never (non-promise "thenable")
type T7 = Awaited<{ then(x: number): void }>; // never (non-promise "thenable")
type T8 = Awaited<{ then(x: () => void): void }>; // unknown
type T9 = Awaited<any>;
type T10 = Awaited<never>;
type T11 = Awaited<unknown>;
type T12 = Awaited<Promise<Promise<number>>>;
type T13 = _Expect<Awaited<Promise<Promise<number>> | string | null>, /*expected*/ string | number | null>; // otherwise just prints T13 in types tests, which isn't very helpful
type T14 = _Expect<Awaited<Promise<Promise<number>> | string | undefined>, /*expected*/ string | number | undefined>; // otherwise just prints T14 in types tests, which isn't very helpful
type T15 = _Expect<Awaited<Promise<Promise<number>> | string | null | undefined>, /*expected*/ string | number | null | undefined>; // otherwise just prints T15 in types tests, which isn't very helpful

interface BadPromise { then(cb: (value: BadPromise) => void): void; }
type T16 = Awaited<BadPromise>; // error

interface BadPromise1 { then(cb: (value: BadPromise2) => void): void; }
interface BadPromise2 { then(cb: (value: BadPromise1) => void): void; }
type T17 = Awaited<BadPromise1>; // error

// https://github.com/microsoft/TypeScript/issues/33562
type MaybePromise<T> = T | Promise<T> | PromiseLike<T>
declare function MaybePromise<T>(value: T): MaybePromise<T>;

async function main() {
let aaa: number;
let bbb: string;
[
aaa,
bbb,
] = await Promise.all([
MaybePromise(1),
MaybePromise('2'),
MaybePromise(true),
])
}

// helps with tests where '.types' just prints out the type alias name
type _Expect<TActual extends TExpected, TExpected> = TActual;


//// [awaitedType.js]
async function main() {
let aaa;
let bbb;
[
aaa,
bbb,
] = await Promise.all([
MaybePromise(1),
MaybePromise('2'),
MaybePromise(true),
]);
}
Loading