Welcome to the Mastering TypeScript series. This series will introduce the core knowledge and techniques of TypeScript.
- Very Useful Tricks for TypeScript as const
- 5 Very Useful Tricks for TypeScript Enum Types
- 5 Very Useful Tricks for TypeScript Never Type
- Advanced Trick for Using TypeScript Interfaces
- What Are K, T, and V in TypeScript Generics?
- Using TypeScript Mapped Types Like a Pro
Challenge
In this challenge, you need to add types to the PromiseAll function, which accepts an array of objects whose elements are Promises or Promise-like objects, and whose return value should be Promise<T>, where T is an array of the results of those Promises.
declare function PromiseAll(values: any): any
const promiseAllTest1 = PromiseAll([1, 2, 3] as const)
const promiseAllTest2 = PromiseAll([1, 2, Promise.resolve(3)] as const)
const promiseAllTest3 = PromiseAll([1, 2, Promise.resolve(3)])
const promiseAllTest4 = PromiseAll<Array<number | Promise<number>>>([1, 2, 3])
type cases = [
Expect<Equal<typeof promiseAllTest1, Promise<[1, 2, 3]>>>,
Expect<Equal<typeof promiseAllTest2, Promise<[1, 2, number]>>>,
Expect<Equal<typeof promiseAllTest3, Promise<[number, number, number]>>>,
Expect<Equal<typeof promiseAllTest4, Promise<number[]>>>,
]In the above code, we used the two utility types Expect and Equal. Their implementation code is as follows:
type Expect<T extends true> = T
type Equal<X, Y> =
(<T>() => T extends X ? 1 : 2) extends
(<T>() => T extends Y ? 1 : 2) ? true : falseSolution
First, let's update the current PromiseAll function as required by the challenge:
declare function PromiseAll<T extends unknown[]>(values: T): Promise<T>In the above code, we have added the type variable T to the PromiseAll function and also used the extends keyword to constrain its type. That is, we constrain the actual type stored in the type variable to be an array type. After updating the PromiseAll function, let's take a look at the first two test cases:

As you can see from the above figure, the first test case has passed, but the second test case has not. Currently typeof promiseAllTest2 returns Promise<[1, 2, Promise<number>]> type, but we expect it to return Promise<[1, 2, number]> type. So the next thing we need to do is to convert the Promise<number> type to number type.
So how do you convert the [1, 2, Promise<number>] type to [1, 2, number]? This is where we need to use TypeScript mapped types, which has the following syntax:

where P in K is analogous to the JavaScript for..in statement in JavaScript, which is used to iterate over all types in the K type, and the T type variable is used to represent any type in TS. If you want to learn more about TypeScript mapping types, I recommend you read the following article.
During the mapping process, we have access to the type of each element, and when we encounter the Promise<number> type, we need to extract the type of the return value of the Promise. For this requirement, we need to use TypeScript conditional types and infer type inference.
declare function PromiseAll<T extends unknown[]>(values: T):
Promise<{
[P in keyof T]: T[P] extends Promise<infer R> ? R : T[P]
}>In the above code, infer R is used to declare a new type variable R. If you are interested in TypeScript conditional types and infer type inference, we recommend you to read the following article.
At this point, the updated PromiseAll function has been able to pass the first two test cases. Let's continue to analyze the last two test cases.

As can be seen from the above figure, currently typeof promiseAllTest3 returns the Promise<(number | Promise<number>)[]> type, and the result we expect is Promise<[number, number, number]>. That is, we hope to obtain the type of each element of the [1, 2, Promise.resolve(3)] array. To achieve this function, we need to use the new feature introduced in TypeScript 4.0 — the tuple spreads syntax supports generics.
declare function PromiseAll<T extends unknown[]>(values: [...T]):
Promise<{
[P in keyof T]: T[P] extends Promise<infer R> ? R : T[P]
}>When we replace the type of the values parameter in the PromiseAll function with the type […T], typeof promiseAllTest3 returns a type of Promise<[number, number, number]>. At this point, however, the last test case has not passed. typeof promiseAllTest4 is of type Promise<(number | Promise<number>)[]>, which means that the type of each element of the array type is of the union type number | Promise<number>.
type T0 = number | Promise<number> extends Promise<number> ? true : false
// type T0 = falseTo solve the above problem, we need to go ahead and update the logic for determining the conditional type in the PromiseAll function:
declare function PromiseAll<T extends unknown[]>(values: [...T]):
Promise<{
[P in keyof T]: T[P] extends infer R | Promise<infer R> ? R : T[P]
}>After updating the PromiseAll function, it passes all the test cases. In fact, there are cleaner ways to handle this than the solution above:
declare function PromiseAll<T extends unknown[]>(values: [...T]):
Promise<{
[P in keyof T]: Awaited<T[P]>
}>In the above code, we are using TypeScript's built-in Awaited<T> utility type, which is used to recursively expand an "awaited type", such as a Promise type.
async function fetchData(): Promise<string> {
return "Hello, Bytefer!";
}
type R0 = ReturnType<typeof fetchData>;
// type R0 = Promise<string>
type R1 = Awaited<ReturnType<typeof fetchData>>;
// type R1 = stringTypeScript is awesome and worth learning, if you like to learn TypeScript, you can follow me on Medium or Twitter to read more about TS and JS!
In Plain English 🚀
Thank you for being a part of the In Plain English community! Before you go:
- Be sure to clap and follow the writer ️👏️️
- Follow us: X | LinkedIn | YouTube | Discord | Newsletter
- Visit our other platforms: Stackademic | CoFeed | Venture
- More content at PlainEnglish.io