Optional chaining (?.) Optional Chaining for JavaScript ( https://github.com/tc39/proposal-optional-chaining ) ¶
See also
Contents
Overview and motivation ¶
When looking for a property value that’s deep in a tree-like structure, one often has to check whether intermediate nodes exist:
var street = user.address && user.address.street;
Also, many API return either an object or null/undefined, and one may want to extract a property from the result only when it is not null
var fooInput = myForm.querySelector('input[name=foo]')
var fooValue = fooInput ? fooInput.value : undefined
The Optional Chaining Operator allows a developer to handle many of those cases without repeating themselves and/or assigning intermediate results in temporary variables:
var street = user.address?.street
var fooValue = myForm.querySelector('input[name=foo]')?.value
When some other value than undefined is desired for the missing case, this can usually be handled with the Nullish coalescing operator:
// falls back to a default value when response.settings is missing or nullish
// (response.settings == null) or when response.settings.animationDuration is missing
// or nullish (response.settings.animationDuration == null)
const animationDuration = response.settings?.animationDuration ?? 300;
The call variant of Optional Chaining is useful for dealing with interfaces that have optional methods:
iterator.return?.() // manually close an iterator
or with methods not universally implemented:
if (myForm.checkValidity?.() === false) { // skip the test in older web browsers
// form validation fails
return;
}
Prior Art ¶
Unless otherwise noted, in the following languages, the syntax consists of a question mark prepending the operator, (a?.b, a?.b(), a?[b] or a?(b) when applicable).
The following languages implement the operator with the same general semantics as this proposal (i.e., 1) guarding against a null base value, and 2) short-circuiting application to the whole chain):
-
C#: Null-conditional operator — null-conditional member access or index, in read access.
-
Swift: Optional Chaining — optional property, method, or subscript call, in read and write access.
-
CoffeeScript: Existential operator — existential operator variant for property accessor, function call, object construction (new a?()). Also applies to assignment and deletion.
The following languages have a similar feature, but do not short-circuit the whole chain when it is longer than one element.
This is justified by the fact that, in those languages, methods or properties might be legitimately used on null (e.g., null.toString() == “null” in Dart):
-
Kotlin: Safe calls — optional property access for read; optional property assignment for write.
-
Dart: Conditional member access — optional property access.
-
Ruby: Safe navigation operator — Spelled as: a&.b
The following languages have a similar feature.
We haven’t checked whether they have significant differences in semantics with this proposal:
-
Groovy: Safe navigation operator
-
Angular: Safe navigation operator
Syntax ¶
The Optional Chaining operator is spelled ?.. It may appear in three positions:
-
obj?.prop // optional static property access
-
obj?.[expr] // optional dynamic property access
-
func?.(…args) // optional function or method call
Semantics ¶
Base case ¶
If the operand at the left-hand side of the ?. operator evaluates to undefined or null, the expression evaluates to undefined.
Otherwise the targeted property access, method or function call is triggered normally.
Here are basic examples, each one followed by its desugaring. (The desugaring is not exact in the sense that the LHS should be evaluated only once and that document.all should behave as an object.)
a?.b // undefined if `a` is null/undefined, `a.b` otherwise.
a == null ? undefined : a.b
a?.[x] // undefined if `a` is null/undefined, `a[x]` otherwise.
a == null ? undefined : a[x]
a?.b() // undefined if `a` is null/undefined
a == null ? undefined : a.b() // throws a TypeError if `a.b` is not a function
// otherwise, evaluates to `a.b()`
a?.() // undefined if `a` is null/undefined
a == null ? undefined : a() // throws a TypeError if `a` is neither null/undefined, nor a function
// invokes the function `a` otherwise
Short-circuiting ¶
If the expression on the LHS of ?. evaluates to null/undefined, the RHS is not evaluated.
This concept is called short-circuiting .
a?.[++x] // `x` is incremented if and only if `a` is not null/undefined
a == null ? undefined : a[++x]
Long short-circuiting ¶
In fact, short-circuiting, when triggered, skips not only the current property access, method or function call, but also the whole chain of property accesses, method or function calls directly following the Optional Chaining operator.
a?.b.c(++x).d // if `a` is null/undefined, evaluates to undefined. Variable `x` is not incremented.
// otherwise, evaluates to `a.b.c(++x).d`.
a == null ? undefined : a.b.c(++x).d
Note that the check for nullity is made on a only.
If, for example, a is not null, but a.b is null, a TypeError will be thrown when attempting to access the property “c” of a.b.
This feature is implemented by, e.g., C# and CoffeeScript; see Prior Art.
Optional chaining ( https://www.martinmck.com/posts/es2020-everything-you-need-to-know/ ) ¶
Optional chaining is probably one of the most highly anticipated features to come to JavaScript in quite some time.
In terms of impact on cleaner JavaScript code, this one scores very highly .
When checking for a property deep inside a nested object, you often have to check for the existence of intermediate objects.
Let’s work through an example.
const test = {
name: "foo",
age: 25,
address: {
number: 44,
street: "Sesame Street",
city: {
name: "Fake City",
lat: 40,
lon: 74
}
}
}
// when we want to check for the name of the city
if (test.address.city.name) {
console.log("City name exists!");
}
// City Name exists!
This works fine! But in software, we can’t always rely on the happy path.
Sometimes intermediate values will not exist. Let’s look at the same example, but with no city value defined.
const test = {
name: "foo",
age: 25,
address: {
number: 44,
street: "Sesame Street"
}
}
if (test.address.city.name) {
console.log("City name exists!");
}
// TypeError: Cannot read property 'name' of undefined
Our code is broken. This happens because we are trying to access name on test.address.city which is undefined. When you attempt to read a property on undefined, the above TypeError will be thrown.
How do we fix this? In a lot of JavaScript code, you will see the following solution.
const test = {
name: "foo",
age: 25,
address: {
number: 44,
street: "Sesame Street"
},
}
if (test.address && test.address.city && test.address.city.name) {
console.log("City name exists!");
}
// no TypeError thrown!
Our code now runs, but we’ve had to write quite a bit of code there to solve the problem.
We can do better .
The Optional Chaining operator of ES2020 allows you to check if a value exists deep inside an object using the new ? syntax .
Here is the above example rewritten using the optional chaining operator.
const test = {
name: "foo",
age: 25,
address: {
number: 44,
street: "Sesame Street"
},
}
// much cleaner.
if (test?.address?.city?.name) {
console.log("City name exists!");
}
// no TypeError thrown!
Looking good. We have condensed the long && chains into our much more succinct and readable optional chaining operator. If any of the values along the chain are null or undefined, the expression simply returns undefined.
The optional chaining operator is very powerful.
Have a look at the following examples to see other ways it can be used.
const nestedProp = obj?.['prop' + 'Name']; // computed properties
const result = obj.customMethod?.(); // functions
const arrayItem = arr?.[42]; // arrays
Articles ¶
https://javascript.info ¶
Slides ¶
https://www.freecodecamp.org ¶
Optional chaining syntax allows you to access deeply nested object properties without worrying if the property exists or not.
If it exists, great! If not, undefined will be returned.
This not only works on object properties, but also on function calls and arrays.
https://www.jesuisundev.com ¶
L’optional chaining fait aussi partie de l’ES2020. Et ça donne du plaisir !
Quand tu dois accéder aux propriétés imbriquées dans un objet, t’es obligé d’être super prudent.
Si tu commences à accéder à une propriété sur un objet qui n’existe pas, ça te pète à la gueule immédiatement
const superToto = {
hero : true,
location: {
city: {
name: "Lyon"
}
},
power: {
psychic: ['telekinesis']
}
}
if(superToto.power && superToto.power.psychic) {
console.log(superToto.power.psychic) //['telekinesis']
}
console.log(superToto.location.country.name) // throw error
L’optional chaining règle ce problème en faisait la vérification à ta place et en renvoyant undefined si la propriété n’existe pas. Il suffit de suivre la convention.
const superToto = {
hero : true,
location: {
city: {
name: "Lyon"
}
},
power: {
psychic: ['telekinesis']
}
}
if(superToto?.power?.psychic) {
console.log(superToto.power.psychic) //['telekinesis']
}
console.log(superToto?.location?.country?.name) // undefined
Fini les prises de têtes à vérifier chaque propriété avant de l’utiliser de peur de se prendre une grosse erreur dans la face.
L’optional chaining nous permet de faire ça safe, de façon super pratique et super simple à lire. J’étais fou quand j’ai vu ça !