arraystypescriptdictionaryvariadic-tuple-types

How to assign Array#map results to variadic tuple `[P in keyof T]: ReturnType<T[P]>`?


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?


Solution

  • 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]