Promise.all
Challenge
Type the function PromiseAll
that accepts an array of PromiseLike
objects.
The returning value should be Promise<T>
where T
is the resolved result
array.
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise<string>((resolve, reject) => {
setTimeout(resolve, 100, "foo");
});
// expected to be `Promise<[number, number, string]>`
const p = Promise.all([promise1, promise2, promise3] as const);
Solution
Interesting challenge here, IMHO. Let me explain it step by step.
We start with the simplest solution - the function that returns Promise<T>
. We
need to return Promise<T>
after all, where T
is an array of types for
resolved promises:
declare function PromiseAll<T>(values: T): Promise<T>;
Now, let us think how to evaluate types for resolved promises. We start with the
fact that values
is an array. So let us show that in our types. By using
variadic tuple types
we can split its types so it is easier to work with its elements:
declare function PromiseAll<T extends unknown[]>(values: [...T]): Promise<T>;
Oops, getting the compilation error “Argument of type ‘readonly [1, 2, 3]’ is
not assignable to parameter of type ‘[1, 2, 3]’.“. That is because we do not
expect to have a readonly
modifier in our values
parameter. Let us fix that
by adding the modifier to the parameter:
declare function PromiseAll<T extends unknown[]>(
values: readonly [...T],
): Promise<T>;
We have a solution that even works on one of the test cases. That is because the
test case does not have promises inside. We return Promise<T>
where T
is
just an array with the same types as in values
parameter. But once we get
Promise
inside the values
parameter, things going wild.
The reason is that we do not unwrap the type from Promise
and just return it
as is. So let us replace the T
with a conditional type to check if the element
is actually a Promise
. If so, we return its inner type, otherwise - the type
with no changes:
declare function PromiseAll<T extends unknown[]>(
values: readonly [...T],
): Promise<T extends Promise<infer R> ? R : T>;
The solution still does not work, because the T
is not a union but the tuple.
So we need to iterate over each element in the tuple and check if the value is a
Promise
or not:
declare function PromiseAll<T extends unknown[]>(
values: readonly [...T],
): Promise<{ [P in keyof T]: T[P] extends Promise<infer R> ? R : T[P] }>;
Comments