typescripttypescript-generics

How to use conditional for unknown type on Typescript


I have this untyped function on a project which executes an async function over a collection, and I want to specify its typings for better IDE assistance - I want the IDE to infer that first parameter at callback is same as the array internal type.

This is the function:

export async function asyncForEach(array, callback) {
    for (let index = 0; index < array.length; index++) {
        await callback(array[index], index, array);
    }
}

I added obvious typings like this but a problem arose:

export async function asyncForEach<T>(
    items: T[],
    callback: (item: T, idx: number, items: T[]) => Promise<any>
): Promise<void> {
    for (let index = 0; index < items.length; index += 1)
        await callback(items[index], index, items);
}

After that I got next scenario:

I'd like to be able to specify a ternary on the callback parameter typing so if it's going to be "unknown", instead of that be "any".

This is one of the several attempts I did, without success - compiler refuses to build and type inferring stops working.

export async function asyncForEach<T>(
    items: T[],
    callback: (item: T extends unknown ? any: T, idx: number, items: T[]) => Promise<any>
): Promise<void> {
    for (let index = 0; index < items.length; index += 1)
        await callback(items[index], index, items);
}

Anyone knows how to solve the (item: T extends unknown ? any: T, part?

Lots of thanks in advance.


Solution

  • I don't know if what you're doing is advisable; my inclination would be: if someone passes in an items parameter of the intentionally unsound any type that turns off type checking wherever it's used, they have to deal with the consequences. In this case, the consequences are that the compiler has no idea what to infer for T, and so it defaults to unknown (as of TypeScript 3.5).

    If you would like to have the compiler choose a different default, you can specify one using the = operator in the type parameter declaration:

    export async function asyncForEach<T = any>(
        items: T[],
        callback: (item: T, idx: number, items: T[]) => Promise<any>
    ): Promise<void> {
        for (let index = 0; index < items.length; index += 1)
            await callback(items[index], index, items);
    }
    

    Note the T = any above. Now you get this behavior:

    declare const a: any;
    asyncForEach(a, async (i) => i.randomThingBecauseAnyDoesntTypeCheck); // okay
    

    which is what you want, without losing the desired behavior with more well-typed items:

    asyncForEach([a], async (i) => i.randomThingBecauseAnyDoesntTypeCheck); // okay
    asyncForEach([1, 2, 3], async (i) => i.toFixed()); // okay
    

    Playground link to code