Name: proposal-optional-chaining
Owner: Ecma TC39
Description: null
Created: 2017-03-09 03:09:55.0
Updated: 2018-05-24 15:27:21.0
Pushed: 2018-03-12 15:25:34.0
Homepage: https://tc39.github.io/proposal-optional-chaining/
Size: 91
Language: HTML
GitHub Committers
User | Most Recent Commit | # Commits |
---|
Other Committers
User | Most Recent Commit | # Commits |
---|
Current Stage:
When looking for a property value that's deep in a tree-like structure, one often has to check whether intermediate nodes exist:
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:
fooInput = myForm.querySelector('input[name=foo]')
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:
street = user.address?.street
fooValue = myForm.querySelector('input[name=foo]')?.value
The call variant of Optional Chaining is useful for dealing with interfaces that have optional methods:
ator.return?.() // manually close an iterator
or with methods not universally implemented:
myForm.checkValidity?.() === false) { // skip the test in older web browsers
// form validation fails
return;
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):
The following languages have a similar feature. We haven?t checked whether they have significant differences in semantics with this proposal:
The Optional Chaining operator is spelled ?.
. It may appear in three positions:
.prop // optional static property access
.[expr] // optional dynamic property access
?.(...args) // optional function or method call
foo?.3:0
to be parsed as foo ? .3 : 0
(as required for backward compatibility), a simple lookahead is added at the level of the lexical grammar, so that the sequence of characters ?.
is not interpreted as a single token in that situation (the ?.
token must not be immediately followed by a decimal digit).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.)
// undefined if `a` is null/undefined, `a.b` otherwise.
null ? undefined : a.b
x] // undefined if `a` is null/undefined, `a[x]` otherwise.
null ? undefined : a[x]
() // undefined if `a` is null/undefined
null ? undefined : a.b() // throws a TypeError if `a.b` is not a function
// otherwise, evaluates to `a.b()`
) // undefined if `a` is null/undefined
null ? undefined : a() // throws a TypeError if `a` is neither null/undefined, nor a function
// invokes the function `a` otherwise
If the expression on the LHS of ?.
evaluates to null/undefined, the RHS is not evaluated. This concept is called short-circuiting.
++x] // `x` is incremented if and only if `a` is not null/undefined
null ? undefined : a[++x]
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.
.c(++x).d // if `a` is null/undefined, evaluates to undefined. Variable `x` is not incremented.
// otherwise, evaluates to `a.b.c(++x).d`.
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 [TODO: provide precise references].
Let?s call Optional Chain an Optional Chaining operator followed by a chain of property accesses, method or function calls.
An Optional Chain may be followed by another Optional Chain.
[3].c?.(x).d
null ? undefined : a.b[3].c == null ? undefined : a.b[3].c(x).d
(as always, except that `a` and `a.b[3].c` are evaluated only once)
Parentheses limit the scope of short-circuting:
b).c
= null ? undefined : a.b).c
That follows from the design choice of specifying the scope of short-circuiting by syntax (like the &&
operator), rather than propagation of a Completion (like the break
instruction) or an adhoc Reference (like an earlier version of this proposal). In general, syntax cannot be arbitrarly split by parentheses: for example, ({x}) = y
is not destructuring assignment, but an attempt to assign a value to an object literal.
Note that, whatever the semantics are, there is no practical reason to use parentheses in that position anyway.
Because the delete
operator is very liberal in what it accepts, we have that feature for free:
te a?.b
elete (a == null ? undefined : a.b) // that *would* work if `? :` could return a Reference...
null ? undefined : delete a.b // this is what we get, really
Although they could be included for completeness, the following are not supported due to lack of real-world use cases or other compelling reasons; see Issue # 22 and Issue #54 for discussion:
new a?.()
a?.`{b}`
new a?.b()
, a?.b`{c}`
The following is not supported, although it has some use cases; see Issue #18 for discussion:
a?.b = c
All the above cases will be forbidden by the grammar or by static semantics so that support might be added later.
[TODO: to be completed. In particular, discuss specific criticisms around long short-circuiting.]
See: https://tc39.github.io/proposal-optional-chaining/
Per the TC39 process document, here is a high level list of work that needs to happen across the various proposal stages.