Last week we talked about Nullish Coalescing, now it's time for another new addition to ECMAScript.
The dreaded existence check
Have you ever worked with an API full of objects that were inconsistently formed? Multiple levels deep and frustrating to parse. Let's use a smaller example to talk about this.
const obj = {
node : {
thing: 1
}
}
Suppose we want to access thing
. We can do it like this.
const thing = obj.node.thing
But what if we can't guarantee that node
exists? If we try and access thing
on undefined
we'll trigger an error. So we'll have to check first.
const thing = obj.node ? obj.node.thing : undefined
This is the terser option, using a ternary, but it works all the same.
This is a reasonable piece of code! But it can get incredibly repetitive if you have deeply nested objects.
Optional Chaining to the Rescue
Well, we don't have to do that anymore. We can use the new optional chaining syntax.
const thing = obj.node?.thing
In this case, obj.node
is undefined
. Typically, trying to access thing
on undefined
would trigger an error, but not in this case! When you use optional chaining it doesn't throw that error and instead evaluates the expression to undefined
.
And what's even cooler is that we can combine this with nullish coalescing.
const obj = {}
const thing = obj.node?.thing ?? 2
// thing will be 2
Since using optional chaining gives us undefined
, thing
will resolve to the value on the right-hand side of the ??
operator. In this case, that's 2
.
Chaining Optional Chaining
Note that my example above uses optional chaining once in order to clarify how it works. However, you can use it multiple times in the same expression.
const thing = obj?.node?.thing
The expression above is valid and may come in handy if obj
looks like this.
const obj = null
The risks
Now I can't write about optional chaining without including a section on warnings. To be honest, a lot of people were really against adding this to the language. They had concerns about abuse, and that's fair!
?.
should NOT replace all instances of .
. If you do that you'll create all kinds of silent failures. Optional chaining is another tool in your belt, that's it.
If you don't control the data you're accessing and it's particularly nested and it's ok if the result doesn't exist then maybe optional chaining is the right choice! But notice all those "and"s in the previous run-on sentence. Make sure you explicitly choose to use this syntax. It shouldn't be your default.
Not just for objects!
Oh, I forgot to mention the best part. Optional chaining works on more than just objects!
It works on arrays.
const tenthItem = arr?.[10]
This makes sure that arr
exists before trying to access the 10th element.
It works for function calls.
const message = obj?.stringFunction()
This makes sure obj
exists before you try and call a function on it.
And it works with top-level objects.
functionDoesNotExist?.()
If this function doesn't exist, it will evaluate to undefined
.
Isn't this fun?
So much power! But remember, that means you have a responsibility to use it wisely!
Top comments (35)
I feel like optional chaining is a real love-hate programming language concept in general because of how it can be used to excuse poorly constructed programs.
Which makes it right at home in JavaScript π
I thought everything about JS is love-hate. :)
Lol, you're not wrong. There is a reason I couldn't write this post without a risks section!
πππ©π«π΅
It's worth mentioning that for those who want to live on the cutting edge, it's supported as an experimental feature now in Chrome 79 π I can't wait for this to be a fully mature feature - it will cut down on many lines of checking for property existence! Or, at least eliminate another need to use an external library function like
lodash.get
!In my opinion this should always have been the default behaviour... Throwing a runtime breaking error when accessing a sub property of an object in a natively asynchronous environment where you cannot ever guarantee that data packet A is going to present when function call B is run 100% of the time is IMO completely stupid.
This is something I will be using everywhere, well for accessing object properties at least. Function calls... Eh probably not, the runtime time part of JavaScript is predictable. It's the data system that's as unpredictable as a Wookie's anger.
I also personally don't see why this needed to be added as an additional bit of syntax... The behaviour of the
conditional ? true : false
statement is predictable and would not be broken by changing the default object access syntax to be conditionally chained by default. obj?.property and obj.property both return the same when trying to access obj.property.thing so... Yeah... Don't get it.IMHO the JavaScript standards body is way to conservative in the way they modify and extend the standard...
Great article! Coming from a Rails background, I've been really missing the
.try
method in JS. Happy to see it's finally landed.That said, it's difficult to discuss optional chaining without talking about the law of demeter, and exactly how much chaining is the right amount.
It's a powerful tool, but it can definitely become overused and cause your application to be brittle!
Ruby has optional chaining too, with
&.
, haven't usedtry
in a long time πTake it for a spin!
What do you need to enable it? Just some Babel stuff?
If you're a fan of react it's now part of react scripts 3.3. And in the latest version of vs code it's supported
Nullish too?
Ya, right now the only way to get the features is Babel. It will be in node soon, but no official announced date.
Wicked cool!
I also didn't know you could use it on arrays and functions which opens up a whole new world... I don't know how many times I've checked an array to exist and have length before using .map() ππ
Wow I've learnt so much in such a short read, awesome post Laurie, hopefully other languages get this.
I call it the call-me-maybe operator.
I learned about this feature in Groovy right around the time "that" song came out
π
maybe in about 5 years we'll actually be able to use it in browsers.
Up to es2018 is supported in just about everything but Internet Explorer. Even Safari and Mobile Safari would run it without babel. Transpiling to ES5 or ES6 these days is just a preference that not everybody does
according to kangax.github.io/compat-table/es20..., Firefox, Safari, and Edge still don't support all of ES2017. maybe we'll have full ES2017 support across all major browsers in 2022, when non-Chromium Edge is expected to finally die.
Beautifully written and consumed with pleasure.
I read somewhere that it could increase bundle size. How true is that?
bundle size of node? Or the project that implements it?
The project that implements it.
Itβs not really been in the wild enough to know. My suspicion is it isnβt a problem if youβre using it as a replacement for existence checks would have anyway. If you put it everywhere then yes, because the implementation is more complex than just accessing things.
This is certainly a good feature, before this I had to use something like lodash get('obj.node.foo', 'default').
You can use optional chaining today in every browser:
const thing = ((obj||{}).node||{}).thing
That said, optional chaining is natively supported in Chrome 80+.
Optional chaining is powerful, but also controversial because it encourages laziness. Overall, I like it, but only if its use is constrained by a sensible style guide.
I'm waiting for the pipeline operator it will be the biggest change for me since es6
Some comments may only be visible to logged-in visitors. Sign in to view all comments.