简体   繁体   中英

typescript : type check optional parameter in a Function passed as arguments

Here is the code: (question follows)

one file contains this (types.ts)


declare global{
    interface AnInterface{
    }
}

export type SomeType<T> = 
    T extends [infer A, infer B] ?
      (fst: A, snd:B) => Promise<B> 
        : never

export async function accept<Z extends keyof AnInterface>(aKey: Z, fn : SomeType<Parameters<AnInterface[Z]>>){
    //do sthg with fn
    const firstArg : any = 1;
    const secondArg : any = 2;
    const fnAsAny = <any>fn;
    const result = await fnAsAny(firstArg,secondArg);
    console.log(result)
}

and in another file ("consumer.ts")

import {accept} from './types';

declare global{
    interface AnInterface{
        one : typeof fone 
        two : typeof ftwo
        three : typeof fthree
        four : typeof ffour
    }
}

export async function fone(a:number, b:number){return a+b;}
export async function ftwo(a:any, b:string){return a+b;}
export async function fthree(a:string, b:number=1){return b;}
export async function ffour(a:string){}


a: accept("one", fone); // OK
b: accept("two", fone); // ERROR BUT ... is expected: Argument of type '(a: number, b: string) => Promise<string>' is not assignable to parameter of type '(fst: string, snd: string) => Promise<string>'.
c: accept("two", ftwo); // OK
d: accept("three", fthree); // Argument of type '(a: string, b?: number) => Promise<number>' is not assignable to parameter of type 'never'.
e: accept("four", ffour); // Compile Error but would like to pass


I would like to define type SomeType<T> so that line labeled d and e would pass the typescript check. Is this possible? problem I have is with optional parameter, for case e , I could add another conditional type check.

Typescript: v 4.1

I've got a simple definition that works for all of your cases, with a small caveat regarding ffour .

We want a function that takes up to two arguments. The second argument could be optional or could be omitted entirely. The function returns a Promise of the type of the second argument.

type MyFunc<A, B> = (a: A, b: B) => Promise<B>;

In the type itself, we state that B is required. This is because a function with optional arguments extends one with required arguments, but not the other way around. If B were optional in the type, then we could never fulfill that contract with a function that had a required second argument.

Note that a function with 0 arguments would pass because it's okay to have a function with less arguments than expected, but not more.

We make a dummy function which does nothing to see if our test cases will be accepted as arguments.

const checkFunc = <A, B>(func: MyFunc<A, B>): void => {}
// pass with 2 required args
checkFunc(
    async (a: number, b: number) => a + b
);
// pass with 2 requied args of different types
checkFunc( 
    async (a: any, b: string) => a + b
);
// pass with optional second argument
checkFunc(
    async (a: string, b: number = 1) => b
);
// FAIL as expected with more than 2 arguments
checkFunc(
    async (a: number, b: number, c: number) => a + b + c
)

If there is no second argument, then what is our return type? Can it be any -- since we could call the function with any ignored second argument -- or can it only ever be undefined ? This is a question of expected behavior so there is no right or wrong answer.

As currently written, single-argument function will pass with any return type as the value of the second argument is unknown .

// pass with void function of single argument
checkFunc (
    async (a: string) => {}
)
// pass with function of single argument returning a value
checkFunc (
    async (a: string) => a
)

However we can change the signature of the function to enforce that only void / undefined can be returned.

// FAIL when returning a value with second argument explicitly disallowed
checkFunc (
    async (a: string, b?: never) => a
)

We can also force failure by explicitly setting the generics of checkFunc rather than allowing them to be inferred.

// FAIL based on generics <string, never> -- must have <string, string> to pass
checkFunc<string, never> (
    async (a: string) => a
)

Typescript Playground Link

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM