I'm trying to create a strongly-typed function that maps over a homogeneous array of functions that return an arbitrary value.
I've experimented with both of these, and while they both successfully return the correct types, I get errors in the return value:
const runAll = <T extends (() => any)[]>(
array: [...T],
): {
[P in keyof T]: ReturnType<T[P]>
} => {
const result = [];
for (let i = 0; i < array.length; i += 1) {
result.push(array[i]());
}
return result;
}
// ^ Error: Type 'any[]' is not assignable to type '{ [P in keyof T]: ReturnType<T[P]>; }'.ts(2322)
const results = runAll([
() => 'hello',
() => 123,
() => true,
]);
// const results: [string, number, boolean]
const runAll = <T extends (() => unknown)[]>(
actions: [...T],
): {
[P in keyof T]: ReturnType<T[P]>;
} => actions.map(action => action());
// ^ Error: Type 'unknown[]' is not assignable to type '{ [P in keyof T]: ReturnType<T[P]>; }'.ts(2322)
const results = runAll([
() => 'hello',
() => 123,
() => true,
]);
// const results: [string, number, boolean]
What is the correct way to match the return type?
The simplest (and perhaps only) way to do this is to explicitly cast from the unknown[]
returned by .map
to the complex return type of runAll
. I know that feels wrong, but you know that the cast will always be safe, so it's not terrible to make it internally within the function. I went off your second example, and also fixed a secondary error I got from the return type. (I probably have a newer TS version.) The result:
type AllResults<T> = {
[P in keyof T]: T[P] extends (() => unknown) ? ReturnType<T[P]> : never;
};
const runAll = <T extends Array<() => unknown>>(
actions: [...T],
): AllResults<T> => actions.map(action => action()) as AllResults<T>;
const results = runAll([
() => 'hello',
() => 123,
() => true,
]);
// const results: [string, number, boolean]