Home > Mobile >  How to make an array check into a function without getting an error?
How to make an array check into a function without getting an error?

Time:10-27

How do I turn the array check into a function, and not get an error in the code where the array check used to be?

The original function body is "start", and I intend to turn the function body into "start1". However, when I do that, an error shows up on the line as noted below.

type JSONRPCParams = object | any[]; // this is in another module where I cannot change the source

async function start(params: JSONRPCParams | undefined) {
    // the intention is to turn the following check into a function
   if (!params || !Array.isArray(params) || params.length === 0) {
       throw new Error('invalid parameter(s) provided');
   }
    
    const assetConfig = params[0]; // this is ok when the check is inline
}

function validateParams(params: JSONRPCParams | undefined) {
   if (!params || !Array.isArray(params) || params.length === 0) {
       throw new Error('invalid parameter(s) provided');
   }
}

async function start1(params: JSONRPCParams | undefined) {
    validateParams(params);    
    const assetConfig = params[0]; // this is not ok, when the check becomes a function
}

The error is: 'params' is possibly 'undefined'.ts(18048) Element implicitly has an 'any' type because expression of type '0' can't be used to index type 'JSONRPCParams'. Property '0' does not exist on type 'JSONRPCParams'.ts(7053)

I also tried writing validateParams as follows, but it doesn't work, the same error as above is seen.

if (params && Array.isArray(params) && params.length >= 0) {
    return;
}
throw new Error('Invalid parameter(s) provided'); 

How do I write the validateParams function so that in the start1 function, I don't get an error on the commented line?

I've researched other similar Stackoverflow questions, but they do not handle this issue.

Your expertise and help is appreciated.

Thank you.

CodePudding user response:

Control flow analysis such as the results of truthiness checking (like if (params) { }) or the results of type guard functions (like the Array.isArray() method) does not propagate across function boundaries. This is a trade-off made in order to make control flow analysis tractable for the compiler; see microsoft/TypeScript#9998 for a large discussion on this general issue. Essentially, any narrowings that occur inside of a function body are not seen outside the function, so the check of the params parameter inside the body of validateParams(xxx) has no effect on the apparent type of xxx outside the function.

If you want this to happen, you need to annotate validateParams as an assertion function, and tell the compiler exactly how the body narrows its argument. For example:

function validateParams(params: JSONRPCParams | undefined): asserts params is any[] {
  if (!params || !Array.isArray(params) || params.length === 0) {
    throw new Error('invalid parameter(s) provided');
  }
}

The return type is the assertion predicate asserts params is any[], which means that after the call to validateParams(xxx), the type of xxx will be narrowed from some type assignable to JSONRPCParams | undefined to some type assignable to any[].

Note that you are informing the compiler of how that function narrows. The compiler does not verify that it does so. You could have replaced the body of validateParams() with just about any logic, and there would be no compiler error. For example, no logic at all:

function validateParams(params: JSONRPCParams | undefined): asserts params is any[] {
  // eh, it's probably fine
}

So be careful.


Anyway, now your start1() function will work as desired:

async function start1(params: JSONRPCParams | undefined) {
  validateParams(params);
  const assetConfig = params[0]; // okay
}

And you can see that validateParams() has the ability to narrow to something more specific than any[], depending on the input:

const x = Math.random() < 0.5 ? [1, 2, 3] : undefined;
// const x: number[] | undefined
validateParams(x);
x
// const x: number[]
x.map(x => x.toFixed(1));

Here x has been narrowed from number[] | undefined to number[], not just any[].

Playground link to code

CodePudding user response:

The answer is as follows, as @jcalz noted, to make the function as an assertion function.

function validateParams(params: JSONRPCParams | undefined): asserts params is any[] {
    if (params && Array.isArray(params) && params.length >= 0) {
        return;
    }
    throw new Error('invalid parameter(s) provided');
}
  • Related