Can someone ask me why Number.isFinite
dont have type guard like number is number
?
THis example provides an error Object is possibly 'undefined'
function inc (n?: number) {
return Number.isFinite(n) ? n + 1 : 1;
}
Playground:
https://www.typescriptlang.org/play?ssl=1&ssc=1&pln=3&pc=2#code/GYVwdgxgLglg9mABDSiAUYD8AuRYQC2ARgKYBOAlIgN4BQiiZJUIZSAcoaWQHQwDOAMRQwoJDFUx5EAakQBGRLnkBuWgF8gA
isFinite is a function property of the global object. You can use this function to determine whether a number is a finite number. The isFinite function examines the number in its argument. If the argument is NaN , positive infinity, or negative infinity, this method returns false ; otherwise, it returns true .
isFinite() The Number. isFinite() method determines whether the passed value is a finite number — that is, it checks that the type of a given value is Number , and the number is neither positive Infinity , negative Infinity , nor NaN .
isFinite() returns true if a value is a finite number. Number. isFinite() returns true if a number is a finite number.
First some background. On first glance, this seems to be related to the difference in JavaScript between
isFinite
which will first convert its argument to number and
Number.isFinite
which happily accepts all arguments, regardless of type, and simply returns true
only for finite numbers (with no implicit conversions). In TypeScript these have signatures
function isFinite(number: number): boolean
(method) NumberConstructor.isFinite(number: unknown): boolean
In the second method, the parameter type is unknown
instead of number
. The reason is that in JavaScript, Number.isFinite
happy returns false
for non-numbers (whereas isFinite
coerces first).
> Number.isFinite(false)
false
> isFinite(false)
true
> Number.isFinite(Symbol(2))
false
> isFinite(Symbol(2))
Uncaught TypeError: Cannot convert a Symbol value to a number at isFinite (<anonymous>)
Now since Number.isFinite
is already expected to accept all arguments from any type and return false
no matter how wacky the arguments, it makes sense for this behavior to be preserved in TypeScript. Hence the parameter type properly is unknown
. For the global isFinite
which is intended to work only on numbers (and does the coercion that TypeScript helps us avoid), it makes sense to restrict the parameter to being a number
.
But as your question rightly asks, why, given that Number.isFinite
will accept any arguments and return true
only for numbers, why can't it be a type guard? The reason is that it returns true for some, but not all numbers! Let's try at least to write our own guard:
function numberIsFinite(n: any): n is number {
return Number.isFinite(n)
}
and these make sense:
console.log(numberIsFinite(20)) // true
console.log(numberIsFinite(Number.MAX_VALUE)) // true
console.log(numberIsFinite(undefined)) // false
console.log(numberIsFinite("still ok")) // false
But here is where things go wrong-ish:
console.log(numberIsFinite(Infinity)) // false but uh-oh
console.log(numberIsFinite(NaN)) // false but uh-oh
Now it's true that with this type guard that
const x: number|string = 3
if (numberIsFinite(x)) {
console.log(`A number whose type is ${typeof(x)}`)
} else {
console.log(`A string whose type is ${typeof(x)}`)
}
will print
"A number whose type is number"
so far so good but:
const x: number|string = Infinity
if (numberIsFinite(x)) {
console.log(`A number whose type is ${typeof(x)}`)
} else {
console.log(`A string whose type is ${typeof(x)}`)
}
will print
"A string whose type is number"
And this is silly.
If there was a dependent type in TS called "finite number" then (and perhaps only then) would making Number.isFinite
a type guard make sense.
Credit to @VLAZ, whose answer this explanation is based on. I added the second part to my original answer after reading theirs.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With