6 ms·
I just did some refactoring on a medium size code base and here are a few things to watch out for when adopting optional chaining and the new null coalescing op
by achou 7y ago
I just did some refactoring on a medium size code base and here are a few things to watch out for when adopting optional chaining and the new null coalescing operator:
foo && await foo();
is not the same as
await foo?.();
this will work in most cases but subtly, the await wraps the undefined case into a Promise, while the original code would skip the await altogether.
String regular expression matching returns null, not undefined, so rewriting code such as:
const match = str.match(/reg(ex)/);
return match && match[1];
is not the same thing as:
return match?.[1];
because the latter returns undefined, not null, in case of match failure. This can cause problems if subsequent code expects null for match failure. An equivalent rewrite would be:
return match?.[1] ?? null;
which is longer than the original and arguably less clear.
A common idiom to catch and ignore exceptions can interact poorly with optional chaining:
const v = await foo().catch(_ => {});
return v?.field; // property 'field' does not exist on type 'void'
This can be easily remedied by changing the first line to:
const v = await foo().catch(_ => undefined);
Of course, these new operators are very welcome and will greatly simplify and help increase the safety of much existing code. But as in all things syntax, being judicious about usage of these operators is important to maximize clarity.
- mattigames 7y agoYou have to watch out for first and last one in JavaScript but not on TypeScript as it isn't possible to make that mistake because you have to type it as a promise or in the last one as void. You can even avoid the problem in the second one by using NonNullable TypeScript types, but I admit that's not common so its still likely to arise.
- achou 7y agoThe first example can happen in TypeScript; foo has type (() => Promise<void>) | undefined admittedly it may not be all that common to have a function-valued variable that may be undefined, but it happened in the code base I was working with. In the last example, you're right that TypeScript will catch this at compile time. My point was to show how this compile time error can happen from refactoring to use optional chaining, and one easy solution in this case.
- paulddraper 7y ago&& ?. || ?? It's a shame JS at the beginning doubled down on the "billon dollar mistake" [1] with two(!) kinds of NULL instead of just using Maybe/Option. Ah well, if it were good it wouldn't be popular :/ [1] https://www.lucidchart.com/techblog/2015/08/31/the-worst-mistake-of-computer-science/ https://www.lucidchart.com/techblog/2015/08/31/the-worst-mis...
- vikingcaffiene 7y agoSorry, JS? While JS might get this stuff one day, these language features are for TypeScript which is its own language. It's strongly typed and just happens to interop with and in some scenarios transpile down to JavaScript. It's whole existence is to deal with that billion dollar mistake you mentioned. Speaking of which, optional chaining and null coalescence are core language features of some very good languages. Kotlin and C# for instance. Kotlin, much like TypeScript, interops with a "broken" language (Java and the JVM in its case) and attempts to address some core deficiencies in that ecosystem. Let us have nice things!! :) I am really excited about these new features and hope they do land in JS sooner rather than later. I hope they do the pipe operator next! `pipe |> operator|> plz`
- svachalek 7y agoThese features are copied from TC39 proposals for JavaScript. Presumably they were deemed safe to add to TS now that they have reached Stage 3 as JS proposals. https://github.com/tc39/proposals https://github.com/tc39/proposals
- ajkjk 7y agoNot so much 'copied', considering it was the TS team that has gotten them added to JS. They just didn't want to commit to anything in the TS codebase that wasn't at least stage 3 in JS.
- ygra 7y agoOut of curiosity, have there been features that were thought to be part of JS one day, added by TypeScript, and then abandoned for JS again, leading TS to rip them out as well? Or do such vestiges remain in TypeScript and have to be changed into different constructs on compilation?