Mastering TypeScript: Reimplementing Promise.all in the Type System
Written on
Chapter 1: Introduction to Promise.all
In the world of JavaScript, Promise.all is a widely used utility that simplifies handling multiple promises concurrently. In this section, we will delve into its implementation within the TypeScript framework.
This paragraph will result in an indented block of text, typically used for quoting other text.
Section 1.1: Understanding Type Challenges
As we dive deeper into TypeScript, we will explore various challenges, specifically focusing on adding types to a custom PromiseAll function. This function is designed to take an array of values, which can include promises or promise-like objects, and return a promise that resolves to an array of the results.
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([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, number, number]>>>,
];
In the code snippet above, we utilize two utility types, Expect and Equal. Their definitions are as follows:
type Expect<T> = T;
type Equal<T, X, Y> =
(() => T extends X ? 1 : 2) extends
(() => T extends Y ? 1 : 2) ? true : false;
Section 1.2: Solution Development
To address the challenge, we first need to enhance the PromiseAll function to include a generic type variable, T, ensuring it reflects the intended return type.
declare function PromiseAll<T>(values: T[]): Promise<Awaited<T>>;
After this update, we can observe how our initial test cases perform. While the first case succeeds, the second case fails to return the expected type of Promise<[1, 2, number]>. The issue arises because typeof promiseAllTest2 currently evaluates to Promise<[1, 2, Promise]>.
To resolve this, we must convert the Promise type to a more specific type, such as number. Utilizing TypeScript's mapped types allows us to achieve this:
declare function PromiseAll<T extends any[]>(values: T): Promise<{
[K in keyof T]: T[K] extends Promise<infer R> ? R : T[K];
}>;
Through this process, we can effectively extract the resolved type from the promise, thus refining our implementation.
Chapter 2: Advanced Techniques
In this video, titled "Promise.all with John Chadwick - TypeScript Type Challenges #20 [MEDIUM]", we will explore the intricacies of implementing Promise.all using TypeScript, including practical examples and common pitfalls.
This next video, "Promise all - TypeScript Type Challenges #20 [MEDIUM]", continues our discussion on the Promise.all function, providing additional context and solutions to common challenges encountered during implementation.
In summary, TypeScript presents powerful tools for managing asynchronous operations effectively. The PromiseAll implementation demonstrates how to leverage generics and mapped types to refine our type handling in complex scenarios. If you're eager to learn more about TypeScript and its capabilities, I invite you to follow my work on Medium or connect with me on social media!
Thank you for being part of the In Plain English community! Remember to keep exploring and learning!