Optional chaining and strictNullChecks issue - javascript

I'm running into a peculiar typescript error when using the > operator with an optional chaining expression.
Example 1 :
if(a?.length === 3)
Example 2 :
if(a?.length > 0)
In both of the above examples, let's say a is inferred as Array<string> | undefined.
Problem: example 1 is totally fine, but example 2 throws a typescript error TS2532: Object is possibly 'undefined'. The error seems to clear off if I update example 2 to if(a && a.length > 0).
I understand it is something to do with strictnullchecks in TS, but no clue why example 1 works fine and doesn't throw an error.
Is it something basic to do with how === and > operators evaluate? In the browser console, undefined > 0 and undefined === 0 evaluates as false, so I would assume there shouldn't be a problem in the if statement too in example 2, which is not the case.
I would appreciate it if someone could enlighten me on why example 2 throws a TS error.

As described in What does "all legal JavaScript is legal TypeScript" mean?, TypeScript may issue type warnings on code that it considers incorrect. JavaScript is very permissive compared to many other languages, and will happily allow you to do things like use the greater-than > operator to compare two completely unrelated values like undefined and 0. The JavaScript specification interprets undefined > 0 as if it were NaN > 0 (using the "not-a-number" NaN value), and NaN compares as false to everything else, no matter what. So undefined > 0 is well-defined in JavaScript. But is it good JavaScript?
That's subjective, but the whole point of TypeScript is that it attempts to overlay a static type system on top of JavaScript. If JavaScript had a stricter type system, it would definitely support x > y where x and y are both numbers, and it also would support it where x and y are both strings. But anything else is probably a mistake. Or at least that's the stance TypeScript takes.
TypeScript will be happy if you refactor that comparison so that you only use > on two numbers, such as by checking whether a is truthy first:
if (a && a.length > 0) { }
In the particular case you bring up, where optional chaining means you are possibly comparing undefined to a number, there is an open feature request to support it in microsoft/TypeScript#45543. It's marked as Awaiting More Feedback, which means the TS team would want to see more demand for it and the reasoning behind it before thinking of implementing it. If you really want to see it happen, you might want to give the issue a 👍, but pragmatically speaking it is unlikely to make much difference.
In that issue it is mentioned that if you want this behavior right now you could use the non-nullish assertion operator ! to pretend that the possibly-undefined value is not undefined, and this will suppress the error:
if (a?.length! > 0) {}
So if you really need to use this technique instead of the type safe version, you can do so without waiting for an unlikely change to the language.
Playground link to code

For the first example, the === is checking for both value and type, so basically you are checking undefined which is the same type of a after the optional chaining ? so the compiler infers that a is actually undefined, meanwhile > can't be used with an undefined value, so TS gives you that error.
you can prevent this issue by eg. adding a type guard if (a)... or if(a!.length > 0).
Hope that help.

Related

Why to use strict comparison in string status checking?

I oftentimes see answers using strict comparison (===) instead of normal comparison (==) on status checking, i.e. here:
if(document.readyState === 'complete') ...
I would understand the reason if it were applied on empty string and the obtained value could be also other falsy value with different meaning. But when applied on non-empty string (like 'complete' in the sample), I believe the result is always the same for '==' and '==='. Is that so?
Some people measured that '===' can be faster, but I haven't seen a real world example where it would make any observable difference, so I don't take this micro-optimalization seriously.
On the other hand, anytime I see this operator, I read it as a warning "mind the type here!". But since document.readyState is always string, it annoys me that the original coder made me to study the code what other types there can appear - only to find out that only string.
To me, it is a strong reason to be polite to those who read my code and never use '===' when the type plays no role in the comparison.
Since '===' appears in similar cases in many SO answers and many expert pages, I would like to know if it is just social bandwagon or if there is any good reason why to use it in status checking.
I would recommend always using '===' instead of '==' when strict equality checking is required, which it is in most cases, for this reason: it declares intent. When I see code with '===', I will read it as 'a must be referentially or primitively equal to b'. When I see '==', I will read it as 'a must be coercibly equal to b'. From that, I will judge what kind of goal the code / original programmer is trying to accomplish and how they are passing data around to get the job done. Essentially, it yields insight into the context of the application, the way data is being passed around, and how this function / method / code block fits into the picture.
With that being said, if I see someone do 'a == b' when they are both strings, I'm not going to get on any high horse and make a fuss about it.
It always depends on what you want to achieve, and what you consider to be equal.
== is not always bad, but it often can lead to false assumptions.
If you have something like this:
class TestA {
toString() {
return 'complete'
}
}
class TestB {
valueOf() {
return 'complete'
}
}
let testA = new TestA()
let testB = new TestB()
console.log(testA == 'complete')
console.log(testB == 'complete')
Then testA == 'complete' might be exactly what you want, but because it does an implicit cast, you might do a false assumption about it being a string when it evaluates to true.
So if you later want to call something like substring on it, or passing it to another function that expects an actual string, then it might fail with an unexpected error.
Using === over == is mostly about maintainability, if you start to refactor code or if you look at older code and you see a == you always need to think about if you really want to have an implicit cast at that point or if it this was an accident. So you need to follow the code flow or check if it was documented.
Using an explicitly and === does not prevent you from doing other mistakes, but it keeps the code consistent.

Why use triple-equal (===) in TypeScript?

In JavaScript, it's commonly seen as best practice to use === instead of ==, for obvious and well-known reasons.
In TypeScript, which is one to be preferred? Is there even one which is preferable to the other one?
IMHO, using === in TypeScript doesn't make sense, since comparison already only works on equal types, hence you won't have the (more or less funny) coercion game as in plain JavaScript. If you take aside compatibility to JavaScript for a minute, TypeScript could even get rid of ===, couldn't it?
short version:
== can do unexpected type conversions, in Javascript 1=="1" is true. The === operator avoids this. Comparing different types with === is always false.
The typescript compiler will emit an error message when you compare different types with ==. This removes the unexpected type conversions that can happen with == in Javascript.
This is a case where valid Javascript leads to an error message in the typescript compiler. The idea that all valid Javascript is also valid Typescript is a common misconception. This is simply not true.
longer version:
I think the accepted answer is misleading. Typescript actually does fix == vs === (as far as possible at least).
In Javascript there are two comparison operators:
== : When comparing primitive values, like numbers and strings, this operator will apply a type conversion before doing the comparison. 1 == "1" evaluates to true.
===: This operator does not do type conversions. If the types don't match it will always return false.
Also, both operators will compare reference types based on their references. Two separate objects are never considered equal to each other, even if they store the same values:
let a = {val:1};
let b = {val:1};
c = a;
a==b; // false
a===b; // false
a==c; //true
a===c; //true
So there you have the two common sources of errors in Javascript comparisons:
comparing different types with == can lead to unexpected type conversions.
comparing objects and arrays is based on references not values stored inside.
As the existing answer already says, Typescript is designed as a superset of Javascript. So it doesn't change the behaviour of these comparison operators. If you write == in Typescript, you get type conversions.
So how is this fixed? With the compiler. If you actually do write code that compares incompatible types with == it's a compiler error. Try compiling the following sample:
let str = "1";
let num = 1;
console.log(str == num);
The compiler will tell you:
comparisons.ts:4:13 - error TS2367: This condition will always return 'false' since the types 'string' and 'number' have no overlap.
4 console.log(str == num);
~~~~~~~~~~
Found 1 error.
It is a common misconception that any valid Javascript is also valid Typescript. This is not true and the code above is an example where the typescript compiler will complain about valid Javascript.
This fixes the first of the two sources of errors: unexpected type conversions. It doesn't deal with the second source of errors: comparisons based on references. As far as I know, when you want to do a comparison based on values stored by the objects, you simply can't use these operators. You'll have to implement your own equals() method.
Also, you may have noticed that the compiler error is wrong. The comparison will not always evaluate to false. I think this is a bug in typescript and have filed an issue.
Imagine you're designing TypeScript from scratch. Essentially, you're trying to optimize for making safer code easier to write (TypeScript design goal 1) with a few caveats which prevent you from doing everything you'd like.
JavaScript compatibility (TypeScript design goal 7)
JavaScript should be valid Typescript with no changes.
CoffeeScript makes no guarantees regarding this, so it can convert all instances of == to === and simply tell users don't rely on =='s behavior. TypeScript cannot redefine == without breaking all JavaScript code that relies on its behavior (despite this having sad implications for 3).
This also implies that TypeScript cannot change the functionality of === to, for example, check the types of both operands at compile time and reject programs comparing variables of different types.
Further, compatibility is not limited to simply JavaScript programs; breaking compatibility also affects JavaScript programmers by breaking their assumptions about the differences between == and ===. See TypeScript non-goal number 7:
Introduce behaviour that is likely to surprise users. Instead have due consideration for patterns adopted by other commonly-used languages.
JavaScript as the target of compilation (TypeScript design goal 4)
All TypeScript must be representable in JavaScript. Further, it should be idiomatic JavaScript where possible.
Really though, the TypeScript compiler could use methods returning booleans for all comparisons, doing away with == and === entirely. This might even be safer for users: define a type-safe equality method on each TypeScript type (rather like C++ operator==, just without overloading).
So there is a workaround (for users comparing classes). unknown or any variables can have their types narrowed before using the type-safe equality method.
Which to prefer
Use === everywhere you would in JavaScript. This has the advantage of avoiding the pitfalls common to ==, and doesn't require you to maintain an additional method. The output of the TypeScript compiler will be close to idiomatic JavaScript. Using == has very much the same pitfalls as JavaScript, particularly when you have any, [], or {} involved. As an exception, using == null to check for null or undefined may save headaches if library code is inconsistent.
A method for reference equality (behavior like === for classes) could be confused with a deep/value recursive equality check. Furthermore, === is widely used in TypeScript, and making your code fall in line with conventions is usually more important than any small bit of type safety.
Your intuition was correct. There's no sense to use === in TypeScript to imitate an equality check. The argument that TS compiles to JS "so you should use what is better in JS" is not valid. Why? Because Typescript ensures that both operands of comparison operator(s) have the same type. When both operands have the same type == and === behave identically. And by "identically" I mean 100% identical not just "alike". So there's no more correct or less correct version when both behave exactly the same way in JavaScript.
I guess other commenters here are just looking for ways to preserve their habit of using === or in other words to rationalize. Unfortunately, pure logic tells otherwise: there's no sense to replace == with ===, unless you're going to modify generated JS code manually which is probably never the case.
I use === exclusively for identity checks (when you compare x to x – the same variables, it's sometimes necessary in library code related to memoization). And my counter of errors related to eqeq operator shows 0.
Example:
const s : string = "s"
const n : number = 1
console.log(s == n)
TS2367: This condition will always return 'false' since the types 'string'
and 'number' have no overlap
My opinion is that one should always use ===.
First line of reasoning: TypeScript does not change == to ===. There're TypeScript translators which just strip types. So using === everywhere leads to more robust code if for some reason you forgot to type check your program or if you (or future maintainer of your code) used type casts to override type safety. Should not happen but many things should not happen.
Second line of reasoning: null == undefined. This is true with TypeScript as well. I think that if one writes if (x == null) it makes code less readable because it implies check for undefined as well and implicit code is less readable than explicit if (x === null || x === undefined). Also subtle bugs might occur if this is not done on purpose.
I don't see any issues when just using === everywhere unconditionally other than aesthetic preferences.

What if I am ok with 'Cannot read property '****' of undefined'?

I have some code which looks like following:
$scope.query.toLowerCase() != 1
If query is not defined - there is an error in the console. Actually the code still produces the result that I want, but what is the right approach of dealing with error? Of course I can check the the variable in advance - but that will lead to more code, not sure of the benefits of hiding errors by having to read more code.
Of course I can check the the variable in advance - but that will lead to more code
Writing more code doesn't make your code bad, unless you are in a code golf competition. If you don't handle your errors and edge cases, then your program will not be reliable. If a line of code throws an error, your code might even terminate prematurely.
An alternative way to deal with possible undefined properties is to define a default value instead of checking if it is undefined.
($scope.query || "").toLowerCase() != 1 // this is enough to fix your expression
or more formally
($scope.query === undefined ? "" : $scope.query).toLowerCase() != 1
This obviously depends on what your purpose is.
Another example:
function add(a, b){
return a + (b || 0); // if b is falsy, assume b is 0
}
add(1, 2); // 3
add(1); // 1
Shortcircuiting is very useful in some cases, but make sure you know exactly how it works because misusing it will create unexpected behaviors.
Its bad practice to do ...
try {
$scope.query.toLowerCase() != 1
} catch(e) {
// do nothing
}
since this obscures errors and if an unexpected error happens you wont know about it which makes debugging a nightmare.
The approved practice is to write a few extra lines of defensive code to protect against the scenario that would lead to an error.
Yes its more verbose but its better than having a codebase which is a nightmare to debug.
in general: I'm outsorcing this task into a tiny helper:
function string(v){ return v==null? "": String(v) }
and I can be sure, that the output is always a String (Type-safety).
string($scope.query).toLowerCase() != 1
In this special case? you are checking against a number, you don't need the toLowerCase()
+$scope.query !== 1
would be even better in this case; an no need to deal with null or undefined.

Is this line from Underscore.js doing equality checking actually necessary?

I've just been looking at the _.isEqual function of Underscore.js and a section of the code goes something like this:
if (a === b) return true;
if (typeof a !== typeof b) return false;
if (a == b) return true;
I'm just wondering if there's any case where the third statement could be reached and evaluate to true?
Edit: Just to be clear, this isn't my own code I'm talking about, I'm reading the source of Underscore, in particular, this line and I was curious about why they're doing that.
I've just been browsing through the Underscore repo and came across a short discussion where someone asked the exact same thing, and it looks like it is actually unnecessary.
Following the algorithm defined by the ECMAScript Language Specification in section 11.9.6 and section 11.9.3 seems to show that no pair of values should return true in the above case.
So, in short, no, that situation is not possible.
The only situation where == and === react unexpectedly is when comparing a literal string ("123") to a constructed string (new String("123")), which would fail the first test.
However, on the second test it gets caught because the constructed string has the type object, but the literal has the type string.
Based on that, I'd say no, the third statement can never be reached, and evaluate to true.
When you use the == operator and the expressions are of different types, JavaScript will generally convert the two into the same type before comparing.
For example, this can happen with null and undefined. null == undefined is true, even though null === undefined is false. However typeof null is "object", while typeof undefined is "undefined". So, in this case you should return false on the second statement.
You can read all the details in the spec (section 11.9.3), it is very involved:
http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf
My initial guess was that it was to work around a broken browser implementation.
However after digging into the git logs for that file it looks like the corresponding line was in the very first underscore.js checkin. (I'm not gonna hunt in the parent documentcloud core.js repo...) You can see it at line 334 of https://github.com/documentcloud/underscore/commit/02ede85b539a89a44a71ce098f09a9553a3a6890 .
So now my guess is that its just cruft that got left in, never completely tested and never cleaned out.

Why use === when you are sure types are equal?

It seems that the strict equality operator is preferred whenever possible - I put my code in JSLint and got the following feedback.
Code:
function log() {
console.log(arguments.length == 1 ? arguments[0] : arguments);
}
Feedback from JSLint:
Problem at line 2 character 34: Expected '===' and instead saw '=='.
I am curious to know what advantages === has over == here. Basically, .length returns a Number, and 1 is a Number as well. You can be 100% sure, so === is just a redundant extra token. Also, checking for the type whilst you know the types will always be the same has no performance advantage either.
So what's actually the reason behind using === here?
The only reason is that you don't have to think whether the comparison you are doing will involve coercion or not. If you stick to using ===, you just have one thing less to worry about.
Of course not everyone agrees. This is why you can disable specific checks in JSlint, if you are sure of what you are doing.
It's not strictly required, as you know the length function is well-tested. But what if length was a bit buggy, and there was a chance it could return false?
In this situation, both 0 and false would be evaluated as false using the == comparison. To test for false (when there is also the chance of 0 being returned), you need to also check the data type, which is where === comes in.
Well, === is supposed to be infinitesimally faster, as it can fail faster (on type mismatch), but that's deep inside the "pointless microoptimizations" territory.
Realistically, I'd advise for ===, even if you are 105% sure - I've been tripped up by my assumptions too many times ("yes, I am positive they are the ... same ... uh ..."). Unless there's a good reason to turn them off, it is better to keep sanity checks enabled in code than trust to do them in your mind.

Categories