I have object with hardcoded array values. This object is as const
because in other part of code I need TS to by aware of all possible values.
Problem is that I cannot use .every()
method on given array from that object. TS is complying about This expression is not callable.
even that code works just fine.
I noticed three things while trying to debug this:
- Opposite method
some
has no problems. - If I remove
as const
from object, there is no errors withevery
but like I said I cannot remove this because TS needs to be aware of all values, not treat them as anynumber
- If I use hardcoded key instead variable on that object, it works (but I need to use this as a function).
Why this does not work? What I'm doing wrong?
const demo = {
a: [44, 23, 77],
b: [22, 45, 55],
} as const
const everyfunction = (key: keyof typeof demo) => demo[key]
.every((value) => value > 40) // This expression is not callable.
const somefunction = (key: keyof typeof demo) => demo[key]
.some((value) => value > 40) // works
const everyfunction2 = () => demo['a']
.every((value) => value > 40) // works
console.log(somefunction('a'))
console.log(everyfunction('a'))
CodePudding user response:
The extended error message provides some more clues:
Each member of the union type '...' has signatures, but none of those signatures are compatible with each other.
So why are we getting this error, and why doesn't some
produce the same result? We need to look in the declaration file for Array
to see what the function signatures for these methods actually are:
every<S extends T>(
predicate: (value: T, index: number, array: T[]) => value is S, thisArg?: any
): this is S[];
every(
predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: any
): boolean;
some(
predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: any
): boolean;
As you can see, every
has an overloaded version whereas some
does not. The overloaded version of every
acts as a type predicate, meaning that the array T
passed in is asserted to be of type S
. This is useful because it allows us to do things like:
const arr = ["a", "a", "a"]; // string[]
if (arr.every((char): char is "a" => char === "a")) {
const typedArr = arr; // "a"[]
}
The problem in your example is that the type of demo[key]
is [44, 23, 77] | [22, 45, 55]
. This type is triggering the overloaded version of every
, giving us two potential function signatures for the call:
<S extends [44, 23, 77]>(value: [44, 23, 77], index: number, array: T[]) => value is S
<S extends [22, 45, 55]>(value: [22, 45, 55], index: number, array: T[]) => value is S
The result of calling every
with these signatures cannot be known by TS at compile time - they are not compatible as they contain different numeric literals. some
on the other hand can be called just fine, since it has no such overload, and instead creates an intersection of the two types 44 | 23 | 77 & 22 | 45 | 55
which makes the signature valid.
The simplest way to get around this is just by asserting the tuples into a wider readonly number[]
before making the every
call:
const everyfunction = (key: keyof typeof demo) => (demo[key] as readonly number[])
.every((value) => value > 40)
You could perhaps wrap this in some kind of helper function to obscure the assertion using generics (bare minimum presented here):
const everyOnObject = <O extends Record<string, readonly number[]>>(obj: O, key: keyof O) => {
return obj[key].every((value) => value > 40);
}
console.log(everyOnObject(demo, "a"));
CodePudding user response:
Define your object structures as interfaces first, and you should be fine.