How to use Promise.all() with Typescript
Its generally best to have arrays with consistent types. You can do the following manually though (passing in generic arguments):
Promise.all<Aurelia, void>(
[aurelia.start(), entityManagerProvider.initialize()
])
.then(results => {
let aurelia = results[0];
aurelia.setRoot();
});
Since Promise::all
is a generic function, you can declare the return types of each promise like this:
Promise.all<Aurelia, void>([
aurelia.start(),
entityManagerProvider.initialize()
])
.then(([aurelia]) => aurelia.setRoot());
At least from TypeScript 2.7.1
onwards, the compiler seems to resolve the types without help, with a syntax like this:
Promise.all([fooPromise, barPromise]).then(([foo, bar]) => {
// compiler correctly warns if someField not found from foo's type
console.log(foo.someField);
});
Hat tip: @JamieBirch (from comment to @AndrewKirkegaard's answer)
If you'd like to keep type-safety, it's possible to extend the native type-definition of the Promise
object (of type PromiseConstructor
) with additional overload signatures for when Promise.all
is called with a finite number of not-necessarily inter-assignable values:
interface PromiseConstructor
{
all<T1, T2>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>]): Promise<[T1, T2]>;
all<T1, T2, T3>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>]): Promise<[T1, T2, T3]>;
...
}
Add as many overloads as you need. This approach provides full type-safety for all elements in the value
argument of the onfulfilled
callback:
Promise.all([1, "string", true]).then(value =>
{
let a: number = value[0]; // OK
let b: number = value[1]; // Type 'string' is not assignable to type 'number'.
...
});
PART 1
There are functions you need to understand A) Promise.all
and B) Promise.then
:
A) the type definition of Promise.all
is a function:
all<T>(values: readonly (T | PromiseLike<T>)[]): Promise<T[]>;
B) the type definition of Promise.then
is a function that is a bit more complex:
then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): Promise<TResult1 | TResult2>;
PART 1.a
Promise.then
's type definition is a lot but it can be broken down into small parts:
then<TResult1 = T, TResult2 = never>
a function then
with 2 generics TResult1, TResult2
. The < >
means we can set and use values inside them later - they are called generics.
The then
function itself: (onfulfilled?: ..., onrejected?: ...): Promise<TResult1 | TResult2>
.
PromiseLike
is a helper type and the same as Promise
(for an intro lesson).
onfulfiled
and onrejected
are functions in the form of: (value: T) => (TResult1 OR PromiseLike<TResult1>)
OR undefined
OR null
. Notice the generic T
is used here.
PART 2 -
Promise itself has a generic interface: interface Promise<T>
. The <T>
is a/the generic.
So when you call
Promise.all<SomeCoolType>([a(), b(), c()]).then( value => doSomething(value) )
your generic is SomeCoolType
and in this example some cool type is
interface SomeCoolType = [A() => string, B() => boolean, C() => number]
Now remember that A B C
have to be Promises. And this makes it so your value
in .then( value => ...
is going to the result of SomeCoolType
which for us is calling all those functions, the result is [string, boolean, number]
.
CONCLUSION -
Concretely, the array of function/promises you pass into you Promise.all<T>
are generics that are used in .then(result => ...)
. The return/resolve value of those promises will become the value/type of result
.
Example: Promise.all<[Promise<() => string>]>([returnStringAsync()]).then(result => console.log(typeof result === "string")); # => true