I would like to check if the passed type is the right type. See code example below. But I don't know how to check based on the return type because the arguments of FunctionA
and FunctionB
are the same.
type TypeA = { a: string };
type TypeB = { a?: string, b: Record<string, any> };
type FunctionA = ( argA: string ) => TypeA | Promise<TypeA>;
type FunctionB = ( argA: string ) => TypeB | Promise<TypeB>;
function isFunctionB(target: FunctionA | FunctionB): target is FunctionB {
return true //How do I check the type without running the function
}
class Test {
myFunction?: FunctionA | FunctionB;
mustBeFunctionB: boolean;
constructor({
myFunction,
mustBeFunctionB
}: {
myFunction?: FunctionA | FunctionB;
mustBeFunctionB: boolean;
}) {
this.myFunction = myFunction;
this.mustBeFunctionB = mustBeFunctionB
if (mustBeFunctionB && myFunction && !isFunctionB(myFunction)) {
throw new Error(
"Invalid constructor arguments! myFunction must be a type of FunctionB"
);
} else {
console.log("Passed")
}
}
}
async function myFunctionA(argA: string): Promise<{ a: string }> {
return { a: "test" };
}
const test = new Test({
myFunction: myFunctionA,
mustBeFunctionB: true
}); // This should fail but it still passed
CodePudding user response:
There's no way to check whether a function conforms to a given signature (including the return type) at runtime without executing it as the type information does not exist then.
The only way I can think of to solve this problem would be to use branded types, like so:
type TypeA = { a: string };
type TypeB = { a?: string, b: Record<string, any> };
type FunctionA = (( argA: string ) => TypeA | Promise<TypeA>) & { __brand: 'A' };
type FunctionB = (( argA: string ) => TypeB | Promise<TypeB>) & { __brand: 'B' };
// ...
const myFunctionA: FunctionA = async (argA: string): Promise<{ a: string }> => {
return { a: "test" };
}
myFunctionA.__brand = 'A'
// ...
function isFunctionB(target: FunctionA | FunctionB): target is FunctionB {
return target.__brand === 'B';
}
This will allow you to difference both types at runtime, given they are defined properly.
You might want to look at ts-brand
if you're planning on reusing this sort of pattern more often.
CodePudding user response:
Solution 1: Method Overloading
You can make use of method overloading to achieve it. Here is a simplified code sample to present the concept.
// if mustBeFunctionB is true, myFunction should be FunctionB
function fun(myFunction: FunctionB, mustBeFunctionB: true): any
// if mustBeFunctionB is false, myFunction could be FunctionA or FunctionB
function fun(myFunction: FunctionA | FunctionB, mustBeFunctionB: false): any
// the base type defination, should be compatible with all types above
function fun(myFunction: FunctionA | FunctionB, mustBeFunctionB: boolean): any {
};
It can also be used for class constructor. See the modification of your code in playground
Solution 2: Union type parameter
You can also declare the input with union to specify the peer of the mustBeFunctionB
flag if you okay with a single object config as in your code.
constructor({
myFunction,
mustBeFunctionB
}: {
myFunction: FunctionB;
mustBeFunctionB: true;
} | {
myFunction?: FunctionA | FunctionB;
mustBeFunctionB: false;
})
Beware that these solutions does not check the exact context on runtime, but just a "type guard" as you stated in the title. However, typescript will prevent you from making wrong input, which is the concept of typescript.