r/javascript • • Feb 20 '18

Optional Chaining may be coming to JavaScript

https://dev.to/sammyisa/optional-chaining-may-be-coming-to-javascript--4ff0
321 Upvotes

100 comments sorted by

62

u/impreson Feb 20 '18

Thanks for sharing. I like this a lot. It always feels tedious doing the checking, especially in nested objects. Syntactic sugar like this makes programming more fun and lets you focus on the important stuff. This is also one of the reasons why I don’t like php syntax, with its isset, empty, is_null etc.

14

u/Rudauke Feb 20 '18

Yeah, this improvement needs to be implemented. Those chained checks are just so cumbersome

2

u/voyti Feb 20 '18

I looked for this a lot and it seems currently the best way is to use lodash _.get function, here's an example if anyone's interested:

_.get(parentObject, 'nested.path', optionalDefault)

But nice to see it as native operator. I like to call it 'soft dot' myself

8

u/[deleted] Feb 20 '18

But that's stringly typed. Not even really fancy linters can help there

1

u/voyti Feb 20 '18

Sure, there are drawback to this method, although I've been using it in a large project for quite some time now and frankly didn't find many practical issues so far. I'm not saying you're wrong obviously, but can you provide some actuals example of when this could cause issues?

3

u/[deleted] Feb 20 '18

I'm a typescript guy (or any statically typed language) so I view stringly typed as the single worst thing possible. The potential to make typos that are difficult to find is just too high for me.

3

u/voyti Feb 20 '18

Okay, I'm still living in a happy untyped land of whateverness, so less of an issue here probably.

4

u/[deleted] Feb 20 '18

While this is a solution, I refuse to use it because of the difficulties it introduces when refactoring nested keys.

1

u/KatyWings Feb 21 '18

The idx macro for babel might be a little bit cooler because it has a smaller runtime perf drop and nicer syntax: https://github.com/dralletje/idx.macro/blob/master/README.md

0

u/[deleted] Feb 20 '18

[deleted]

5

u/Wizhi Feb 20 '18 edited Feb 20 '18

Haven't tried PHP 7 yet (have to get to it), but coming from C# this just looks wrong.

If $specimen === null, then $specimen->arms should throw a NullReferenceException - or trigger an error, I don't recall quite how PHP handles it - but that doesn't seem to happen which, after having been away from PHP for a couple of years, is weird.

Your example is pretty arbitrary though, as the manual says that sizeof (and thereby count)

Returns the number of elements in array_or_countable. When the parameter is neither an array nor an object with implemented Countable interface, 1 will be returned. There is one exception, if array_or_countable is NULL, 0 will be returned.

so $var should be 0 regardless. It also triggers an error though, but with how PHP uses functions such as count to get such information, it seems really bothersome to actually use null coalescing to get proper results. You'd need to supply weird default values as function parameters - but I guess that's why count returns 0 for null values, though that still throws an error, so you'd need to do an isset() prior to getting the count() unless you want to use the default value, which is just ugly.

This turned much longer than I initially figured. Boy do I appreciate C#'s var count = specimen?.Arms?.Count ?? 0 though.

1

u/the_argus Feb 20 '18

Dart has this too

27

u/beforan Feb 20 '18

So glad to see this. I adopted the c# equivalent so fast when it came out. Plus we use Babel in our workflow where I work so I guess I can use this right now!

11

u/vinnl Feb 20 '18

Plus we use Babel in our workflow where I work so I guess I can use this right now!

But if syntax changes or it even never makes it to the standard as well, you will have to update your codebase.

9

u/[deleted] Feb 20 '18

Small price to pay. In-fact you could probably codemod it out if needs be.

10

u/vinnl Feb 20 '18

If such a codemod is available/you're able/willing to write it yourself. In any case, it's a good thing to be aware of before blindly adopting it - I'm afraid many people aren't afraid what "stage 1" means.

1

u/alchemoria Feb 20 '18

Your slashes are confusing

2

u/vinnl Feb 20 '18

Sorry I know, I wasn't up to spending more time on reworking that comment :P

1

u/beforan Feb 20 '18

Definitely a valid point

0

u/[deleted] Feb 20 '18

[deleted]

3

u/vinnl Feb 20 '18

Hehe :)

1

u/[deleted] Feb 20 '18

Pretty sure this one will stick:

  • solves a common pain point

  • makes sense in both OO and functional contexts

  • similar syntax is present in multiple languages

  • transpiration produces very little ES5, so it is likely that the engine makers won't have too hard a job to implement it.

2

u/vinnl Feb 21 '18

Those are all very good points in favour of adopting it now - very helpful for people wanting to weigh their options to see whether it's worth it to them.

(Apart from you probably meaning to refer to transpilation :P)

1

u/[deleted] Feb 21 '18

Autocorrect never fails to amuse :)

14

u/[deleted] Feb 20 '18

4

u/[deleted] Feb 20 '18

aaka The Elvis operator

10

u/jasie3k Feb 20 '18

Elvis and safe navigation operators are not the same.

3

u/daringStumbles Feb 20 '18

In every language except C# you are correct, the safe navigation operator is referred to as the Elvis operator in C#. I don't know why, but it's weird that way.

2

u/sebnukem Feb 20 '18

No, the Elvis op is ?:

12

u/[deleted] Feb 20 '18

This is the best thing since bread.slice()

30

u/r_iceposeidon_irl Feb 20 '18

Bread?.slice?.()

14

u/poopie_pants Feb 20 '18

Oh god. What have we unleashed.

11

u/SpliceVW Feb 20 '18

As a community, we just need to use this responsibily. Nothing worse than eating what should have been an error early and allowing the application flow to continue, causing downstream errors and making the real error location harder to track down.

19

u/phpdevster Feb 20 '18 edited Feb 21 '18

I don't mind this coming to JS since different tools for different jobs and all that, but it's been my experience that if you have to do this extensively, you have some design problems with your code whereby objects may or may not be in a reliably complete state at a given time. It means you either have the wrong abstractions, the wrong data models, are grotesquely violating the Law of Demeter, or are assembling data models at the wrong points in your codebase.

Your goal should be to design your domain and application such that you are always working with proper, well formed objects that do in fact have the data and properties you expect them to have. If you only need partial data from a model and don't want to have to populate the whole thing, then your data model is too tightly coupled to too many responsibilities and you should break it apart. If a function is asking such a deep chain of data, then stop passing in the root and pass in only the leaf that you need. If you are doing logic based on the existence or non-existence of some data in an object, stop and more carefully think about the responsibilities of the functions you're passing the potentially incomplete data into.

As convenient as foo?.bar?.baz?.() is, it's really hiding a code smell. At least before when you did all that explicit null checking, you had to stop and think if that was really the right design for the code, and that maybe something was amiss.

8

u/1-800-BICYCLE Feb 20 '18

I totally agree and was hoping to see more productive discussion about this.

On one hand, JS is a scripting language at heart, so it makes sense to have first-class support for dealing with maybe-null values. Furthermore, any JS dev has had to deal with deep nullable path traversal at some point.

On the other hand, I've code reviewed way too many cases where 1) design-by-contract or early checks (at parse time, for example) should be used instead (e.g. your consumers make the assumption that the value being passed to them exists and your producers make damned well sure that they do).

There's also another problem that tends to crop up: hyper-defensive coding that adds unnecessary noise and makes debugging and refactoring harder. For example, defining a nested object literal on line 1 and then using the ?. operator to access it on line 2. If for some reason that object you defined on line 1 doesn't exist (misspelling, comic ray flipping bits, etc) then you better damned well find out about it sooner rather than later.

3

u/mrarnoldpalmer Feb 20 '18

Definitely agree with this. After writing a large project that heavily uses propOr and pathOr from ramda we have unknowingly hidden a lot of issues with the data contract. We used it as a crutch instead of doing proper up front data normalization and now we are kicking ourselves and refactoring a big part of the app to help fix these issues. I'm in favor of this feature, its just easy to abuse.

2

u/dphrageth Feb 21 '18

I think you're definitely hitting on something. I'm at the start of a code rework and knew that in the code there were a few, legitimate IMO, uses of this nested object checking that at the time I worked carefully to ensure were legitimate and not bad design, but I was excited to replace them with something more convenient.

But as I started coding I completely and inappropriately started dropping this new syntax in everywhere because I wanted to. It does make it slightly too easy, especially because it looks so similar to a regular nested accessor. That said, still looks/functions well where rarely required.

But this is where we can actually flip this in a positive manner. This syntax + a warn lint rule == even better than before. Looks beautiful AND forces me to think it through before I lint-disable that warn.

18

u/[deleted] Feb 20 '18 edited Feb 20 '18

I need this in my life today. I'd say undefined is the biggest annoyance and probably the only major one remaining I have with Javascript. Currently using lodash get for this purpose.

Edit: wanted to expand why lodash get is not good enough for me. I don't mind using library but lodash get doesn't work with TypeScript well.

3

u/calligraphic-io Feb 20 '18

Have you looked at using algebraic data types, like Sanctuary.js?

3

u/[deleted] Feb 20 '18

No.

0

u/0987654231 Feb 20 '18

It's worth looking at, algebraic data types(ADTs) can be a powerdul too.

1

u/Auride Feb 20 '18

I don't imagine it would be particularly hard to implement a TS equivalent yourself, just for this purpose. At least the array variation is pretty simple:

function get(
  objectToAccess:Object,
  path:Array<string|symbol>,
  defaultValue:any = undefined
):any{
  let objUnshelled = objectToAccess;
  for (const key of path) {
    const nextObj = objUnshelled[key];
    if (nextObj === undefined) {
      return defaultValue;
    } else {
      objUnshelled = nextObj;
    }
  }
  return objUnshelled;
}

The string variants are slightly more complicated, but it probably reduces down to applying String.split() and maybe another parse step or two to handle array indexing.

1

u/[deleted] Feb 20 '18

I'm not sure what your function solves. Lodash has TypeScript definitions. The issue is second parameter being string. So you any use autocompletion and it's prone to errors and refactoring is not available either. You also have to cast type. In short, it sucks compared to proposed native way.

1

u/[deleted] Feb 20 '18

With TypeScript I'm using those solutions:

Use _.get instead of _.has:

_.get(myObject, "key.subkey", <any_default_value_if_property_does_not_exist>)

If you need to use _.has, use the exclamation mark operator in TypeScript to tell the compiler that those keys exist.

if (_.has(myObject, "key.subkey")){
	myObject!.key!.subkey = "foo";
}

But yeah you need to force the type if the interface of the object wasn't created.

21

u/vanceForceOne Feb 20 '18

Heh,

Reminds me of something... Coffeescript.... I will say it was one of the nicer features.

4

u/zwacky Feb 20 '18

I want this. Only thin off-putting is that it's in proposal stage-1 since so long.

1

u/[deleted] Feb 20 '18

Live a little

3

u/propelol Feb 20 '18

I prefer using destructuring and defaulting, which works today:

const {
  arms: arms = []
} = specimen

arms.length > 2

1

u/NoInkling Feb 21 '18

It's not the easiest thing to read however. Especially if you try and go more than one level deep.

1

u/propelol Feb 22 '18 edited Feb 22 '18

It surprisingly easy to go multiple levels deep. It looks weird at first, but you'll get used to it:

const {
  limbs: {
    arms: arms = 0
  } = {},
  head: {
    eyes: {
      leftEye: leftEye = true,
      rightEye: rightEye = false
    } = {},
  } = {}
} = specimen

Note: this does more than the optional chaining does, since it also defaults the missing values, and pull them out of the object

1

u/NoInkling Feb 22 '18

Firstly, you know that if you want your variables to be named the same as your properties, you can simplify that a bit right?

const {
  limbs: {
    arms = 0
  } = {},
  head: {
    eyes: {
      leftEye = true,
      rightEye = false
    } = {}
  } = {}
} = specimen

Secondly, I know you can do it (and can get used to reading it, like most things), but that doesn't help other people who are attempting to make sense of it. It doesn't just look "weird", it looks like a horrible mess. I'd much rather have the far more intuitive (imo) proposal.

In saying that, I'm not opposed to using (and have used) destructuring defaults in basic cases, e.g. in your original:

const { arms = [] } = specimen

But there's no way I'd use the nested form.

6

u/[deleted] Feb 20 '18

Happy to see this. JavaScript is becoming better and better.

I used to make fun of it ten years ago, but it's improved more than any other language I know.

Also what is great is that even if you don't like JavaScript, you can use another language that simply compiles into it, and you get to benefit from the platform and its tooling.

2

u/jpsi314 Feb 20 '18

I agree with others that this kind of code can be a code smell. But I'm often dealing with APIs where fields may or may not be present in which case this operator would be very helpful in reducing clutter.

3

u/nothingduploading Feb 20 '18 edited Feb 20 '18

ok. neato.

1

u/CCB0x45 Feb 20 '18

Really want this my code is littered with these. I also want a way to bind methods of JS classes with their own this. I have so many of these in my constructors: this.myfunc = this.myfunc.bind(this)

2

u/monvillalon Feb 20 '18

1

u/CCB0x45 Feb 20 '18

Yea but it should ebe handled without a plugin or library

1

u/Extracted Feb 21 '18

I love this one

1

u/soulsizzle Feb 20 '18

I also want a way to bind methods of JS classes with their own this.

Arrow functions do this.

1

u/CCB0x45 Feb 20 '18

I know arrow functions do this but it's nice to write using class syntax when you are writing a class.

1

u/SahinK Feb 20 '18

You probably shouldn't call a class member function from a different context anyway. Why do you need this?

For callbacks you can use

functionThatTakesCallback(() => this.method());

instead of

functionThatTakesCallback(this.method);

1

u/CCB0x45 Feb 20 '18

Event handlers callbacks is the typical case, you can use your syntax or use my syntax binding in the constructor, I think the way I do it is more readable when writing react and view classes. I'd just like a way to do it by default.

1

u/Extracted Feb 21 '18

Sending functions to child components in React and avoiding creating a new function on every render

1

u/[deleted] Feb 20 '18

Awesome, I always wanted something like this but never knew it existed already in at least one language (C#). Hope this gets adopted, this is a nice quality of life feature.

1

u/sebnukem Feb 20 '18

Yes! It's one of the reasons I keep using Groovy.

1

u/Pesthuf Feb 20 '18

How much work is it to make a Stage 0 proposal for TC39?

I'd really like to get some discussion on Dart-inspred Cascade notation - A sort of operator (..) that works like "." expect that it returns the left hand side object - that would allow having jQuery-like, fluent APIs for basically every object (even existing, ancient APIs), without creating the overhead that a fluent API wrapper or even a whole library would, or forcing people to use return this in every method instead of returning something potentially useful.

Basically, it's supposed to allow things like

new XMLHttpRequest()..open('GET', '/test.json')..onload = () => console.log('Done!')..send();

or

document.querySelector('#div')
..textContent = 'Hi!'
..addEventListener('click', () => console.log('hi!'))
..classList.add('very-important');

Do you need to already write parser rules and everything for a stage 0 strawman? Are proposals welcome at all?

0

u/[deleted] Feb 21 '18

[deleted]

1

u/Pesthuf Feb 21 '18

True, but can't the same be done for this optional chaining feature as well?

Also the classlist part is a bit dirty, since it's no longer part of the cascade. You can't do

..classlist.add('lol')
..setAttribute('whatever', 'whatever')

after it.

I've always felt like using functions like this for this problem seems overkill. I probably wouldn't do it in my code. One less dependency.

1

u/chabv Feb 21 '18

Swift and Rust have Optional chaining. Bringing it to JS would be a real pleasure. Combine that with functional programming and immutable data structures. Null might be a thing of the past.

1

u/DukeBerith Feb 20 '18

Cool!

For anyone wanting to use this today and is already using lodash or underscore, use the _.get method

_.get(object, 'path.to[0][1].deeply.nested.freakin.item', optionallyDefaultValue)

https://lodash.com/docs#get

4

u/bart2019 Feb 20 '18

Meh.

That means the code has to parse the path, every single time, and then, traverse it.

1

u/DukeBerith Feb 22 '18

Nah.

Lodash's stuff tends to be memoised, and a quick skim in the source code shows that the path gets computed once and stored for future reuse. It only is parsed every time if that path is dynamic and is different every single time.

1

u/bart2019 Feb 22 '18

It still needs a loop.

1

u/Bluecewe Feb 20 '18 edited Feb 20 '18

The dot notation is great (object?.property). However, it seems a little strange for bracket notation (object?.['property']) and function invocation (exampleFunction?.(argument)). Indeed, if you were to drop the ? in either, and run it in JavaScript today, an exception would be thrown.

I'd have hoped that JavaScript engines could handle brackets and functions with only the ?, allowing these three forms:

object?.property;
object?['property'];
exampleFunction?(argument);

The proposal authors have briefly justified this in their FAQs:

We don’t use the obj?[expr] and func?(arg) syntax, because of the difficulty for the parser to distinguish efficiently those forms from the conditional operator, e.g. obj?[expr].filter(fun):0 and func?(x - 2) + 3 :1.

Hopefully this difficulty can be overcome for the final standard. There's a lot of discussion about this in the proposal repository. It would be nice to avoid awkward syntax like the current proposal.

3

u/AEternal Feb 20 '18

I would prefer it to stay ?. because that particular convention has already been adopted by other languages, thus it would be more familiar.

1

u/Bluecewe Feb 20 '18

Unfortunately, I'm not familiar with those other languages. I'd be curious to see their syntax in action if you could point me in their direction.

Just to be clear though, the dot notation in itself is perfectly fine where it makes sense. In fact, it would be quite odd if it were missing in those contexts. However, the proposal is to use the dot notation in places where the . would throw an exception if the ? were missing: for bracket notation and function invocations. For instance, the following code will throw an exception in your browser:

var object = {a: 1};
object.['a'] = 2; // SyntaxError: Unexpected token [

It's in contexts like these that it would be nicer to omit the .. So:

object?.property; // This is perfectly fine.
object?.['property']; // This is a little strange and inconsistent.
object?['property']; // This would make more sense and be consistent with the rest of the language.

In the languages that you hinted at, is the second or third syntax used for bracket notation?

1

u/AEternal Feb 20 '18

I definitely agree with you on the brackets. If they can find a way around it for brackets, I’d be much happier with it.

1

u/palparepa Feb 20 '18

I don't think they mean "difficult" as in difficult for the parser, but for the programmer of the parser.

Perl 6 solved this by changing the conditional operator to ?? ::

1

u/Bluecewe Feb 20 '18

That's possible. I assumed it was for performance reasons because they used the term 'efficiently'. Perhaps, as you suggest, they mean 'efficient to implement', meaning it's simple to implement.

0

u/erm_what_ Feb 20 '18

It also makes sense to distinguish because it doesn't call the function, only check if it exists (AFAIK)

2

u/bart2019 Feb 20 '18

It first checks that it exists, and if it does, calls it.

2

u/Bluecewe Feb 20 '18

Hmm, I'm not sure. The purpose of optional chaining is not to check whether something exists as such, but rather to access it if it does exist, and return undefined if it does not. Thus, you might want to invoke a function if it exists, and optional chaining would allow you to do so safely without additional code. Plus, in most other parts of the language, () invokes a function or instantiates an instance of a class. Because of this, it would be inconsistent and confusing if, in optional chaining, the same syntax didn't invoke the function, but simply checked for the existence of it. You might also want to retrieve the function if it exists instead of invoking it, in which case you would not include the ().

1

u/kor0na Feb 20 '18

Ok that's cool but can we have the operator be 👏?

species 👏 arms 👏 length

1

u/SustainedSuspense Feb 20 '18

🙏 this will clean up so much code

-1

u/Koala_T_User Feb 20 '18

I really hope this doesn’t come to js. It’s so awkward to debug in c#. Just imo

4

u/0987654231 Feb 20 '18

What would you suggest as an alternative? Do you think what we have now is fine?

3

u/Koala_T_User Feb 20 '18

Honestly I do. It’s an unpopular opinion but yeah I don’t like the Elvis operator in c#

1

u/0987654231 Feb 20 '18

i think that opinion is fair, I'm not sure that what we currently have is good enough either though.

3

u/CCB0x45 Feb 20 '18

How would it be hard to debug, you can. Always see the object in the debugger at they line and the result of the expression.

1

u/Koala_T_User Feb 20 '18

I didn’t say hard I said awkward. It’s like nullable bools, they have value but also a trade off in clarity.

3

u/daringStumbles Feb 20 '18

I think you need to spend some more time learning the debugger in Visual Studio. This is not at all true. You can literally hover over each piece of the expression and a tooltip tells you what it evaluates to. Not only that, but you can use the immediate window to evaluate your own expressions in the same scope, as well as look at the object in the autos. There are about half a dozen ways to see the value while debugging.

1

u/Koala_T_User Feb 20 '18

No I write c# all day and know each of the techniques you’ve listed. It’s not that debugging it is particularly difficult, I just don’t appreciate the uncertainty it brings with it. There’s nothing wrong with a differing opinion and I respect yours

1

u/daringStumbles Feb 20 '18

I guess I'm just not certain why you think it brings more uncertainty, the expression is either null, or the fully evaluated nested expression, there isn't an inbetween. In most cases I've found safe navigation brings both clarity and readability to what would otherwise be a very nested/chained Boolean expression (exactly what most people in js have to do). You stated it was awkward to debug, but then said it's not the debugging. I guess I tend to think that if someone new to c# or .net, or development in general would cone across your post, that is the response I would give.

I would agree there a situations that people can go a little over the top with them and sometimes a single if statement adds more clarity, but I think that is then an incorrect use of the safe navigation. All said though, it is a small price to pay over the added security of knowing your application is less likely to throw null reference exceptions. Adding it to the js spec would further js robustness, "being liberal in what you accept and strict in what you do". It makes it easier and cleaner for developers to account for situations where objects are unset, even if they are supposed to be under normal conditions.

1

u/1-800-BICYCLE Feb 20 '18

Let's not get it twisted: the simplicity is in syntax only. Cyclomatic complexity checkers still report each ?. as a branch because they ultimately expand out to a deeply-nested if statement (technically deeply-nested ternaries since they're expressions). Add to that the fact that people use them multiple times per function, and quickly the execution path becomes deceptively complex.

1

u/daringStumbles Feb 20 '18

Right, but without the syntactic sugar, I've seen plenty of situations where developers simply don't do the checks, and then null reference exceptions litter the logs. It makes devs more likely to account for the potential null.

0

u/simquad Feb 20 '18

Makes it more readable (IMO), so easier to debug?

0

u/piedrose Feb 20 '18

Very cool

0

u/DOG-ZILLA Feb 20 '18

This is cool!

0

u/basiclaser Feb 20 '18

existential chaining like coffee? :D

hey?.thats?.pretty?.good

0

u/jasie3k Feb 20 '18

Recently I wanted to introduce this to our commercial project but I faced the opposition because this feature is only in stage 1 and it's not clear if or when it's coming to be a part of a standard.

1

u/brunoscopelliti Feb 21 '18

This sounds reasonable. In the past new features were withdrawn despite being at a more advanced stage (Object.observe for instance)... and you usually don't want to find yourself in that situation.