简体   繁体   中英

How to check the type of an object in TypeScript when it has multiple return type?

I have a method which has multiple return types. Here, one type is array and the remaining all are single object. I want to a check if the return type is an array then use loop else not.

I can detect array type by using as but when I add if..else condition, then I am getting // Getting Property 'forEach' does not exist on type 'output1Model | output2Model | output3Model[]'. Property 'forEach' does not exist on type 'output1Model'. // Getting Property 'forEach' does not exist on type 'output1Model | output2Model | output3Model[]'. Property 'forEach' does not exist on type 'output1Model'. error.

Why it is targeting output1Model even though return type is indicating all three?

CODE:

Service.ts

myFunction(input: someModel): output1Model | output2Model | output3Model[] {
    // Some logic. 
   // Return type would be either of these 3 based on condition.
}

controller.ts

const checkThis = Service.myFunction(input);

// ERROR: Getting Property 'forEach' does not exist on type 'output1Model | output2Model | output3Model[]'.
// Property 'forEach' does not exist on type 'output1Model'.
if (checkThis as output3Model[]) {
    checkThis.forEach(element => {
        // DO SOMETHING
    });
} else {
     // DO SOMETHING
}

There are a few misconceptions here:

  • checkThis as output3Model[] is not detecting whether checkThis is an output3Model[] , it is asserting that it is one. Put another way: you are telling the compiler, not asking it.

  • type assertions only have affects on the expression they are used in and do not propagate to subsequent uses of the asserted variable. So while you could write (checkThis as output3Model[]).forEach(...) to narrow the perceived type of checkThis so that the compiler doesn't complain, you cannot write (checkThis as output3Model[]) and then later checkThis.forEach() . The second use of checkThis is not narrowed. If you want "check and persist" behavior, you need to make use of control flow analysis . Otherwise you'd have to keep asserting for every use of checkThis .

  • a type assertion is only a way to communicate with the compiler about what types you expect to see. It is part of the static type system, and is therefore erased from the emitted JavaScript. Your code above ends up running in JavaScript as something like:

     if (checkThis) { checkThis.forEach(element => { // DO SOMETHING }); }

    As such, you can see that all you are doing to "detect" the type of checkThis is making sure it isn't falsy . Presumably output1Model and output2Model are some objects and therefore not falsy, so this check will always try running checkThis.forEach() even if checkThis does not have a forEach() method. You need a runtime check , not a type system artifact .


All that means that the compiler is treating checkThis as the original type output1Model | output2Model | output3Model[] output1Model | output2Model | output3Model[] output1Model | output2Model | output3Model[] , and it complains that since it doesn't know for sure that it has a forEach() method, it is not safe to try to call it.

The answer "Why it is targeting output1Model even though return type is indicating all three?" has to do with the way the error messages in TypeScript are generated. If you have a value v of type A | B | C | D | E | F A | B | C | D | E | F A | B | C | D | E | F and you try to use it in a way that is only applicable to A , the compiler could generate a list of five error messages like " v might be an B , and if so, you can't do what you're doing." and "Or v might be a C , and if so, you can't do what you're doing." and so on up to "Finally, v might be an F , and if so, you can't do what you're doing". But really, any one of those error messages is enough to tell you you're making a mistake. So it just picks the first one it comes to. The fact that v might be a B is enough to generate the warning about why B doesn't let you do what you're trying to do.


Summing up, you need to use a runtime check which the compiler can analyze to verify that you are treating checkThis like an array only when it is sure to be one. There are a few ways to do this.

My advice would be Array.isArray() , whose type signature in TypeScript is that of a type guard function . If Array.isArray(checkThis) returns true at runtime, then the compiler knows than any subsequent use of checkThis in the control flow can be treated as an array, and thus an output3Model[] . And if it returns false , then the compiler knows it is not an array, and thus must be an output1Model | output2Model output1Model | output2Model (assuming those are not array types):

if (Array.isArray(checkThis)) {
  checkThis.forEach(element => {
    // (parameter) element: output3Model
  });
} else {
  checkThis
  // const checkThis: output1Model | output2Model
}

Playground link to code

I bet you should ensure that your checkThis is an array in another way, something like this:

if (Array.isArray(checkThis)) {
    checkThis.forEach(element => {
        // DO SOMETHING
    });
} else {
     // DO SOMETHING
}

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