Skip to content

Commit ea521d4

Browse files
authored
Adds 'Awaited' type alias and updates to Promise.all/race/allSettled/any (#45350)
* Adds 'Awaited' type alias and updates to Promise.all/race/allSettled/any * Use Awaited<T> with 'await' * Clean up overloads * Further restrict 'Awaited<T>' auto-wrapping for 'await'
1 parent cdd9314 commit ea521d4

38 files changed

+2142
-290
lines changed

src/compiler/checker.ts

+184-71
Large diffs are not rendered by default.

src/harness/fourslashInterfaceImpl.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1095,6 +1095,7 @@ namespace FourSlashInterface {
10951095
typeEntry("PromiseConstructorLike"),
10961096
interfaceEntry("PromiseLike"),
10971097
interfaceEntry("Promise"),
1098+
typeEntry("Awaited"),
10981099
interfaceEntry("ArrayLike"),
10991100
typeEntry("Partial"),
11001101
typeEntry("Required"),

src/lib/es2015.iterable.d.ts

+2-10
Original file line numberDiff line numberDiff line change
@@ -203,23 +203,15 @@ interface PromiseConstructor {
203203
* @param values An iterable of Promises.
204204
* @returns A new Promise.
205205
*/
206-
all<T>(values: Iterable<T | PromiseLike<T>>): Promise<T[]>;
206+
all<T>(values: Iterable<T | PromiseLike<T>>): Promise<Awaited<T>[]>;
207207

208208
/**
209209
* Creates a Promise that is resolved or rejected when any of the provided Promises are resolved
210210
* or rejected.
211211
* @param values An iterable of Promises.
212212
* @returns A new Promise.
213213
*/
214-
race<T>(values: Iterable<T>): Promise<T extends PromiseLike<infer U> ? U : T>;
215-
216-
/**
217-
* Creates a Promise that is resolved or rejected when any of the provided Promises are resolved
218-
* or rejected.
219-
* @param values An iterable of Promises.
220-
* @returns A new Promise.
221-
*/
222-
race<T>(values: Iterable<T | PromiseLike<T>>): Promise<T>;
214+
race<T>(values: Iterable<T | PromiseLike<T>>): Promise<Awaited<T>>;
223215
}
224216

225217
interface String {

src/lib/es2015.promise.d.ts

+2-74
Original file line numberDiff line numberDiff line change
@@ -18,79 +18,7 @@ interface PromiseConstructor {
1818
* @param values An array of Promises.
1919
* @returns A new Promise.
2020
*/
21-
all<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(values: readonly [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike<T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>, T8 | PromiseLike<T8>, T9 | PromiseLike<T9>, T10 | PromiseLike<T10>]): Promise<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]>;
22-
23-
/**
24-
* Creates a Promise that is resolved with an array of results when all of the provided Promises
25-
* resolve, or rejected when any Promise is rejected.
26-
* @param values An array of Promises.
27-
* @returns A new Promise.
28-
*/
29-
all<T1, T2, T3, T4, T5, T6, T7, T8, T9>(values: readonly [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike<T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>, T8 | PromiseLike<T8>, T9 | PromiseLike<T9>]): Promise<[T1, T2, T3, T4, T5, T6, T7, T8, T9]>;
30-
31-
/**
32-
* Creates a Promise that is resolved with an array of results when all of the provided Promises
33-
* resolve, or rejected when any Promise is rejected.
34-
* @param values An array of Promises.
35-
* @returns A new Promise.
36-
*/
37-
all<T1, T2, T3, T4, T5, T6, T7, T8>(values: readonly [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike<T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>, T8 | PromiseLike<T8>]): Promise<[T1, T2, T3, T4, T5, T6, T7, T8]>;
38-
39-
/**
40-
* Creates a Promise that is resolved with an array of results when all of the provided Promises
41-
* resolve, or rejected when any Promise is rejected.
42-
* @param values An array of Promises.
43-
* @returns A new Promise.
44-
*/
45-
all<T1, T2, T3, T4, T5, T6, T7>(values: readonly [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike<T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>]): Promise<[T1, T2, T3, T4, T5, T6, T7]>;
46-
47-
/**
48-
* Creates a Promise that is resolved with an array of results when all of the provided Promises
49-
* resolve, or rejected when any Promise is rejected.
50-
* @param values An array of Promises.
51-
* @returns A new Promise.
52-
*/
53-
all<T1, T2, T3, T4, T5, T6>(values: readonly [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike<T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>]): Promise<[T1, T2, T3, T4, T5, T6]>;
54-
55-
/**
56-
* Creates a Promise that is resolved with an array of results when all of the provided Promises
57-
* resolve, or rejected when any Promise is rejected.
58-
* @param values An array of Promises.
59-
* @returns A new Promise.
60-
*/
61-
all<T1, T2, T3, T4, T5>(values: readonly [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike<T4>, T5 | PromiseLike<T5>]): Promise<[T1, T2, T3, T4, T5]>;
62-
63-
/**
64-
* Creates a Promise that is resolved with an array of results when all of the provided Promises
65-
* resolve, or rejected when any Promise is rejected.
66-
* @param values An array of Promises.
67-
* @returns A new Promise.
68-
*/
69-
all<T1, T2, T3, T4>(values: readonly [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike<T4>]): Promise<[T1, T2, T3, T4]>;
70-
71-
/**
72-
* Creates a Promise that is resolved with an array of results when all of the provided Promises
73-
* resolve, or rejected when any Promise is rejected.
74-
* @param values An array of Promises.
75-
* @returns A new Promise.
76-
*/
77-
all<T1, T2, T3>(values: readonly [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>]): Promise<[T1, T2, T3]>;
78-
79-
/**
80-
* Creates a Promise that is resolved with an array of results when all of the provided Promises
81-
* resolve, or rejected when any Promise is rejected.
82-
* @param values An array of Promises.
83-
* @returns A new Promise.
84-
*/
85-
all<T1, T2>(values: readonly [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>]): Promise<[T1, T2]>;
86-
87-
/**
88-
* Creates a Promise that is resolved with an array of results when all of the provided Promises
89-
* resolve, or rejected when any Promise is rejected.
90-
* @param values An array of Promises.
91-
* @returns A new Promise.
92-
*/
93-
all<T>(values: readonly (T | PromiseLike<T>)[]): Promise<T[]>;
21+
all<T extends readonly unknown[] | []>(values: T): Promise<{ -readonly [P in keyof T]: Awaited<T[P]> }>;
9422

9523
// see: lib.es2015.iterable.d.ts
9624
// all<T>(values: Iterable<T | PromiseLike<T>>): Promise<T[]>;
@@ -101,7 +29,7 @@ interface PromiseConstructor {
10129
* @param values An array of Promises.
10230
* @returns A new Promise.
10331
*/
104-
race<T>(values: readonly T[]): Promise<T extends PromiseLike<infer U> ? U : T>;
32+
race<T extends readonly unknown[] | []>(values: T): Promise<Awaited<T[number]>>;
10533

10634
// see: lib.es2015.iterable.d.ts
10735
// race<T>(values: Iterable<T>): Promise<T extends PromiseLike<infer U> ? U : T>;

src/lib/es2020.promise.d.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,13 @@ interface PromiseConstructor {
1717
* @param values An array of Promises.
1818
* @returns A new Promise.
1919
*/
20-
allSettled<T extends readonly unknown[] | readonly [unknown]>(values: T):
21-
Promise<{ -readonly [P in keyof T]: PromiseSettledResult<T[P] extends PromiseLike<infer U> ? U : T[P]> }>;
20+
allSettled<T extends readonly unknown[] | []>(values: T): Promise<{ -readonly [P in keyof T]: PromiseSettledResult<Awaited<T[P]>> }>;
2221

2322
/**
2423
* Creates a Promise that is resolved with an array of results when all
2524
* of the provided Promises resolve or reject.
2625
* @param values An array of Promises.
2726
* @returns A new Promise.
2827
*/
29-
allSettled<T>(values: Iterable<T>): Promise<PromiseSettledResult<T extends PromiseLike<infer U> ? U : T>[]>;
28+
allSettled<T>(values: Iterable<T | PromiseLike<T>>): Promise<PromiseSettledResult<Awaited<T>>[]>;
3029
}

src/lib/es2021.promise.d.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,12 @@ interface PromiseConstructor {
1919
* @param values An array or iterable of Promises.
2020
* @returns A new Promise.
2121
*/
22-
any<T>(values: (T | PromiseLike<T>)[] | Iterable<T | PromiseLike<T>>): Promise<T>
22+
any<T extends readonly unknown[] | []>(values: T): Promise<Awaited<T[number]>>;
23+
24+
/**
25+
* 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.
26+
* @param values An array or iterable of Promises.
27+
* @returns A new Promise.
28+
*/
29+
any<T>(values: Iterable<T | PromiseLike<T>>): Promise<Awaited<T>>
2330
}

src/lib/es5.d.ts

+13
Original file line numberDiff line numberDiff line change
@@ -1472,6 +1472,19 @@ interface Promise<T> {
14721472
catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): Promise<T | TResult>;
14731473
}
14741474

1475+
/**
1476+
* Recursively unwraps the "awaited type" of a type. Non-promise "thenables" should resolve to `never`. This emulates the behavior of `await`.
1477+
*/
1478+
type Awaited<T> =
1479+
T extends null | undefined ? T : // special case for `null | undefined` when not in `--strictNullChecks` mode
1480+
T extends object ? // `await` only unwraps object types with a callable then. Non-object types are not unwrapped.
1481+
T extends { then(onfulfilled: infer F): any } ? // thenable, extracts the first argument to `then()`
1482+
F extends ((value: infer V) => any) ? // if the argument to `then` is callable, extracts the argument
1483+
Awaited<V> : // recursively unwrap the value
1484+
never : // the argument to `then` was not callable.
1485+
T : // argument was not an object
1486+
T; // non-thenable
1487+
14751488
interface ArrayLike<T> {
14761489
readonly length: number;
14771490
readonly [n: number]: T;

tests/baselines/reference/asyncArrowFunction9_es2017.errors.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ tests/cases/conformance/async/es2017/asyncArrowFunction/asyncArrowFunction9_es20
1616
!!! error TS1005: ',' expected.
1717
~~~~~~~
1818
!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'Promise' must be of type 'PromiseConstructor', but here has type 'any'.
19-
!!! related TS6203 /.ts/lib.es2015.promise.d.ts:150:13: 'Promise' was also declared here.
19+
!!! related TS6203 /.ts/lib.es2015.promise.d.ts:78:13: 'Promise' was also declared here.
2020
~
2121
!!! error TS1005: ',' expected.
2222
~~

tests/baselines/reference/asyncArrowFunction9_es5.errors.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ tests/cases/conformance/async/es5/asyncArrowFunction/asyncArrowFunction9_es5.ts(
1616
!!! error TS1005: ',' expected.
1717
~~~~~~~
1818
!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'Promise' must be of type 'PromiseConstructor', but here has type 'any'.
19-
!!! related TS6203 /.ts/lib.es2015.promise.d.ts:150:13: 'Promise' was also declared here.
19+
!!! related TS6203 /.ts/lib.es2015.promise.d.ts:78:13: 'Promise' was also declared here.
2020
~
2121
!!! error TS1005: ',' expected.
2222
~~

tests/baselines/reference/asyncArrowFunction9_es6.errors.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ tests/cases/conformance/async/es6/asyncArrowFunction/asyncArrowFunction9_es6.ts(
1616
!!! error TS1005: ',' expected.
1717
~~~~~~~
1818
!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'Promise' must be of type 'PromiseConstructor', but here has type 'any'.
19-
!!! related TS6203 /.ts/lib.es2015.promise.d.ts:150:13: 'Promise' was also declared here.
19+
!!! related TS6203 /.ts/lib.es2015.promise.d.ts:78:13: 'Promise' was also declared here.
2020
~
2121
!!! error TS1005: ',' expected.
2222
~~
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
tests/cases/compiler/awaitedType.ts(18,12): error TS2589: Type instantiation is excessively deep and possibly infinite.
2+
tests/cases/compiler/awaitedType.ts(22,12): error TS2589: Type instantiation is excessively deep and possibly infinite.
3+
4+
5+
==== tests/cases/compiler/awaitedType.ts (2 errors) ====
6+
type T1 = Awaited<number>;
7+
type T2 = Awaited<Promise<number>>;
8+
type T3 = Awaited<number | Promise<number>>;
9+
type T4 = Awaited<number | Promise<string>>;
10+
type T5 = Awaited<{ then: number }>;
11+
type T6 = Awaited<{ then(): void }>; // never (non-promise "thenable")
12+
type T7 = Awaited<{ then(x: number): void }>; // never (non-promise "thenable")
13+
type T8 = Awaited<{ then(x: () => void): void }>; // unknown
14+
type T9 = Awaited<any>;
15+
type T10 = Awaited<never>;
16+
type T11 = Awaited<unknown>;
17+
type T12 = Awaited<Promise<Promise<number>>>;
18+
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
19+
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
20+
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
21+
22+
interface BadPromise { then(cb: (value: BadPromise) => void): void; }
23+
type T16 = Awaited<BadPromise>; // error
24+
~~~~~~~~~~~~~~~~~~~
25+
!!! error TS2589: Type instantiation is excessively deep and possibly infinite.
26+
27+
interface BadPromise1 { then(cb: (value: BadPromise2) => void): void; }
28+
interface BadPromise2 { then(cb: (value: BadPromise1) => void): void; }
29+
type T17 = Awaited<BadPromise1>; // error
30+
~~~~~~~~~~~~~~~~~~~~
31+
!!! error TS2589: Type instantiation is excessively deep and possibly infinite.
32+
33+
// https://github.com/microsoft/TypeScript/issues/33562
34+
type MaybePromise<T> = T | Promise<T> | PromiseLike<T>
35+
declare function MaybePromise<T>(value: T): MaybePromise<T>;
36+
37+
async function main() {
38+
let aaa: number;
39+
let bbb: string;
40+
[
41+
aaa,
42+
bbb,
43+
] = await Promise.all([
44+
MaybePromise(1),
45+
MaybePromise('2'),
46+
MaybePromise(true),
47+
])
48+
}
49+
50+
// non-generic
51+
async function f1(x: string) {
52+
// y: string
53+
const y = await x;
54+
}
55+
56+
async function f2(x: unknown) {
57+
// y: unknown
58+
const y = await x;
59+
}
60+
61+
async function f3(x: object) {
62+
// y: object
63+
const y = await x;
64+
}
65+
66+
async function f4(x: Promise<string>) {
67+
// y: string
68+
const y = await x;
69+
}
70+
71+
async function f5(x: Promise<unknown>) {
72+
// y: unknown
73+
const y = await x;
74+
}
75+
76+
async function f6(x: Promise<object>) {
77+
// y: object
78+
const y = await x;
79+
}
80+
81+
// generic
82+
83+
async function f7<T>(x: T) {
84+
// NOTE: T does not belong solely to the domain of primitive types and either does
85+
// not have a base constraint, its base constraint is `any`, `unknown`, `{}`, or `object`,
86+
// or it has a non-primitive base constraint with a callable `then`.
87+
88+
// y: Awaited<T>
89+
const y = await x;
90+
}
91+
92+
async function f8<T extends any>(x: T) {
93+
// NOTE: T does not belong solely to the domain of primitive types and either does
94+
// not have a base constraint, its base constraint is `any`, `unknown`, `{}`, or `object`,
95+
// or it has a non-primitive base constraint with a callable `then`.
96+
97+
// y: Awaited<T>
98+
const y = await x;
99+
}
100+
101+
async function f9<T extends unknown>(x: T) {
102+
// NOTE: T does not belong solely to the domain of primitive types and either does
103+
// not have a base constraint, its base constraint is `any`, `unknown`, `{}`, or `object`,
104+
// or it has a non-primitive base constraint with a callable `then`.
105+
106+
// y: Awaited<T>
107+
const y = await x;
108+
}
109+
110+
async function f10<T extends {}>(x: T) {
111+
// NOTE: T does not belong solely to the domain of primitive types and either does
112+
// not have a base constraint, its base constraint is `any`, `unknown`, `{}`, or `object`,
113+
// or it has a non-primitive base constraint with a callable `then`.
114+
115+
// y: Awaited<T>
116+
const y = await x;
117+
}
118+
119+
async function f11<T extends { then(onfulfilled: (value: unknown) => void): void }>(x: T) {
120+
// NOTE: T does not belong solely to the domain of primitive types and either does
121+
// not have a base constraint, its base constraint is `any`, `unknown`, `{}`, or `object`,
122+
// or it has a non-primitive base constraint with a callable `then`.
123+
124+
// y: Awaited<T>
125+
const y = await x;
126+
}
127+
128+
async function f12<T extends string | object>(x: T) {
129+
// NOTE: T does not belong solely to the domain of primitive types and either does
130+
// not have a base constraint, its base constraint is `any`, `unknown`, `{}`, or `object`,
131+
// or it has a non-primitive base constraint with a callable `then`.
132+
133+
// y: Awaited<T>
134+
const y = await x;
135+
}
136+
137+
async function f13<T extends string>(x: T) {
138+
// NOTE: T belongs to the domain of primitive types
139+
140+
// y: T
141+
const y = await x;
142+
}
143+
144+
async function f14<T extends { x: number }>(x: T) {
145+
// NOTE: T has a non-primitive base constraint without a callable `then`.
146+
147+
// y: T
148+
const y = await x;
149+
}
150+
151+
async function f15<T extends { then: number }>(x: T) {
152+
// NOTE: T has a non-primitive base constraint without a callable `then`.
153+
154+
// y: T
155+
const y = await x;
156+
}
157+
158+
async function f16<T extends number & { then(): void }>(x: T) {
159+
// NOTE: T belongs to the domain of primitive types (regardless of `then`)
160+
161+
// y: T
162+
const y = await x;
163+
}
164+
165+
166+
// helps with tests where '.types' just prints out the type alias name
167+
type _Expect<TActual extends TExpected, TExpected> = TActual;
168+

0 commit comments

Comments
 (0)