Reputation: 3530
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'.
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
}
Upvotes: 0
Views: 856
Reputation: 328618
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[]
, 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
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
(assuming those are not array types):
if (Array.isArray(checkThis)) {
checkThis.forEach(element => {
// (parameter) element: output3Model
});
} else {
checkThis
// const checkThis: output1Model | output2Model
}
Upvotes: 3
Reputation: 7428
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
}
Upvotes: 0