Optional-chaining "all to the right"? - javascript

For deeply nested property access, is there a JS operator similar to the "optional chaining" operator which applies to all properties/methods/etc. to the right?
Consider an object with deep nesting, where some properties are null:
const sort = payload.meta.displayConfig.properties.search.sort;
// ^---null? ^---null? ^--null?
This can be handled with the optional-chaining operator:
const sort = payload.meta.displayConfig?.properties?.search?.sort;
But is there another operator where "all calls to the right" are handled as if they were preceded by the optional-chaining operator? Observe:
const sort = payload.meta.displayConfig?!.properties.search.sort;
// ^^---fictional nested-optional-chaining
// operator
In this example (with the fictional ?! nested-optional-chaining operator), if anything from displayConfig and onward to the right (properties, search) are null or undefined, then execution is short-circuited as if each property was preceded by ?.
Is there any discussion around adding such a feature?

The short answer is no. You have to put ?. between each segment if you want to make each segment safe against null/undefined values. And as far as I can tell from looking through the different proposals for ecmascript there hasn't been any discussion of an operator like you're talking about.
Before the optional chaining operator was a thing, many libraries would implemented their own ways to get an attribute in a safe way, some of which behave closer to what you're wanting. For example, in lodash you can do _.get(payload, 'meta.displayConfig.properties.search.sort') However, now that the optional chaining operator is a thing, I would prefer just using it between every segment instead of using these library functions.

Related

What does the myObj.param?.otherParam operator do and how is it used?

I was reading the fundamentals guide for React Navigation and in the section for passing parameters to routes I came across this bit of code that I've never seen before.
if (route.params?.post) {
// Do something
}
I've never seen the ? operator used that way, I've only used the ternary operator. When searching the only other thing I found is the nullish assignment operator ??=.
I fiddled with it in the console and it seems to check if param exists so that if it doesn't exist it doesn't error when asking for .post
My first thought was that is a ternary operator without the second argument, but the third argument appears to be required.
So my question is, in the above code block, what is the ? doing, what is that called, and how/when is it used?
Thanks
It's called optional chaining. You can use it to check if the preceding variable is null, so that you could spare yourself checking for null/undefined properties.
// want to access blub.test.smth
if(blub && blub.test) {
// possibly access blub.test.smth
const value = blub.test.smth;
}
vs
const value = blub?.test?.smth

What does this JavaScript expression mean?

I am working with react-navigation and I can't figure out the meaning of this syntax.
React.useEffect(() => {
if (route.params?.post) { <<<<<WHAT IS THIS ?
// Post updated, do something with `route.params.post`
// For example, send the post to the server
}
}, [route.params?.post]);
Does it work like obect.doesPropertyExist.subProperty or something else?
I have tried going through MDN documentation but can't find any reference to this type of syntax. I am not able to use similar syntax on a random object in my node REPL.
It is called Optional Chaining. Provided in MDN doc:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining
The optional chaining operator (?.) permits reading the value of a
property located deep within a chain of connected objects without
having to expressly validate that each reference in the chain is
valid. The ?. operator functions similarly to the . chaining operator,
except that instead of causing an error if a reference is nullish
(null or undefined), the expression short-circuits with a return value
of undefined. When used with function calls, it returns undefined if
the given function does not exist.
if(route.params.post) then do something.
It checks if route.params has object post
It's called Optional Chaining operator. And it's shorthand for
route.params && route.params.post

Destructuring an optionally chained object, to get the ...rest?

I have this setup
const { discard_me, ...rest } = some?.optional?.chaining;
I end up with an error that discard_me doesn't exist, but that's expected if chaining is also non-existent. It seems like the optional chaining should cover the concerns of both the left and right hand side of the assignment.
Is there a way round this without nullish coalescing?
It seems like the optional chaining should cover the concerns of both
the left and right hand side of the assignment.
It doesn't, because some?.optional?.chaining is either going to resolve to:
some.optional.chaining (which may be undefined); or
undefined (which definitely is).
For the destructuring assignment, the right-hand side must be an object.

Why is `(foo) = "bar"` legal in JavaScript?

In Node.js's REPL (tested in SpiderMonkey as well) the sequence
var foo = null;
(foo) = "bar";
is valid, with foo subsequently equal to "bar" as opposed to null.
This seems counterintuitive because one would think the parenthesis would at least dereference bar and throw Invalid left-hand side in assignment`.
Understandably, when you do anything interesting it does fail in aforementioned way.
(foo, bar) = 4
(true ? bar : foo) = 4
According to ECMA-262 on LeftHandExpressions (so far as I can interpret) are no valid non-terminals that would lead to a parenthetical being accepted.
Is there something I'm not seeing?
It's valid indeed. You're allowed to wrap any simple assignment target in parenthesis.
The left hand part of the = operation is a LeftHandSideExpression as you correctly identified. This can be tracked down through the various precendence levels (NewExpression, MemberExpression) to a PrimaryExpression, which in turn might be a Cover­Parenthesized­Expression­And­Arrow­Parameter­List:
( Expression[In, ?Yield] )
(actually, when parsed with target PrimaryExpression, it's a ParenthesizedExpression).
So it's valid by the grammar, at least. Whether it's actually valid JS syntax is determined by another factor: early error static semantics. Those are basically prose or algorithmic rules that make some production expansions invalid (syntax errors) in certain cases. This for example allowed the authors to reuse the array and object initialiser grammars for destructuring, but only applying certain rules. In the early errors for assignment expressions we find
It is an early Reference Error if LeftHandSideExpression is neither an ObjectLiteral nor an ArrayLiteral and IsValidSimpleAssignmentTarget of LeftHandSideExpression is false.
We can also see this distinction in the evaluation of assignment expressions, where simple assignment targets are evaluated to a reference that can be assigned to, instead of getting the destructuring pattern stuff like object and array literals.
So what does that IsValidSimpleAssignmentTarget do to LeftHandSideExpressions? Basically it allows assignments to property accesses and disallows assignments to call expressions. It doesn't state anything about plain PrimaryExpressions which have their own IsValidSimpleAssignmentTarget rule. All it does is to extract the Expression between the parentheses through the Covered­Parenthesized­Expression operation, and then again check IsValidSimpleAssignmentTarget of that. In short: (…) = … is valid when … = … is valid. It'll yield true only for Identifiers (like in your example) and properties.
As per #dlatikay's suggestion, following an existing hunch, research into CoveredParenthesizedExpression yielded a better understanding of what's happening here.
Apparently, the reason why a non-terminal cannot be found in the spec, to explain why (foo) is acceptable as a LeftHandExpression, is surprisingly simple. I'm assuming you understand how parsers work, and that they operate in two separate stages: Lexing and Parsing.
What I've learned from this little research tangent is that the construct (foo) is not technically being delivered to parser, and therefor the engine, as you might think.
Consider the following
var foo = (((bar)));
As we all know, something like this is perfectly legal. Why? Well you visually can just ignore the parenthesis while the statement remains making perfect sense.
Similarly, here is another valid example, even from a human readability perspective, because the parentheses only explicate what PEMDAS already makes implicit.
(3 + ((4 * 5) / 2)) === 3 + 4 * 5 / 2
>> true
One key observation can be loosely derived from this, given an understanding of how parsers already work. (remember, Javascript still is being parsed (read: compiled) and then run) So in a direct sense, these parentheses are "stating the obvious".
So all that being said, what exactly is going on?
Basically, parentheses (with the exception of function parameters) are collapsed into proper groupings of their containing symbols. IANAL but, in lay man's terms, that means that parentheses are only interpreted to guide the parser how to group what it reads. If the context of the parentheses is already "in order", and thus does not require any tweaking of the emitted AST, then the (machine) code is emitted as if those parentheses did not exist at all.
The parser is more or less being lazy, assuming the parens are impertinent. (which in this edge-case, is not true)
Okay, and where exactly is this happening?
According to 12.2.1.5 Static Semantics: IsValidSimpleAssignmentTarget in the spec,
PrimaryExpression: (CoverParenthesizedExpressionAndArrowParameterList)
Let expr be CoveredParenthesizedExpression of CoverParenthesizedExpressionAndArrowParameterList.
Return IsValidSimpleAssignmentTarget of expr.
I.E. If expecting primaryExpression return whatever is inside the parenthesis and use that.
Because of that, in this scenario, it does not convert (foo) into CoveredParenthesizedExpression{ inner: "foo" }, it converts it simply into foo which preserves the fact that it is an Identifier and thus syntactically, while not necessarily lexically, valid.
TL;DR
It's wat.
Want a little more insight?
Check out #Bergi's answer.

Has anyone proposed a Pipe operator for javascript?

Many languages have an operator that allows you to pipe the results of one operation into a call to another (e.g. the | operator in bash, the |> operator in F#).
One of the great advantages to my mind of the common idiom of method chaining in javascript is that it reads top-to-bottom, left-to-right:
var fooOddSquares = [1, 2, 3, 4, 5]
.filter(x => x % 2)
.map(x => "foo" + x * x)
.reduce(((acc, str, i) => acc[i + 1] = str; return acc), {});
// => {1: "foo1", 2: "foo9", 3: "foo25"}
compared to compositional code:
var something = func5(
func4(
func3(
func2(
func1(
somedata
)
)
)
)
);
which reads right-to-left, bottom-to-top. I realize this could be cleaned up via function composition, but that's not necessarily the point. Just to be absolutely clear with what I'm looking for:
var something = func1(somedata)
|> func2
|> func3
|> func4
//etc...
Doing a google search on the pipe operator in javascript mostly turns up info about the bitwise OR operation. With some digging however I was able to dig up this article describing a dirty-hacked version of operator overloading that could implement something of what I'm talking about. I also unearthed this gist that describes said operator and says that "it has been proposed for javascript".
Looking at ES 2016, I see proposals for an exponentiation operator and the bind operator. Both are useful, but not what I want. So per the headline in the gist, has anyone actually proposed this for javascript?
A pipeline operator has been proposed for ES7 (ES2016) in December 2015.
https://github.com/mindeavor/es-pipeline-operator
As mentioned already a pipeline operator has been proposed for ES7 (2016), however that doesn't help much if you want to use something like this right now with babel which exactly how I came across this question 9 months later.
The biggest hitch in supporting the es-pipeline-operator proposal through babel as far as I know is the current inability to use |> or <| as operators which create syntax errors and cannot be fixed without changes to babel and unfortunately does not look like the problem will be resolved any time soon.
I would personally like to see the pipe backward operator added to the proposal as well since both forward and backward are useful in different situations.
For example, I use the pipe backward operator when modifying functions or anywhere I would typically use "compose" over "pipe" which I prefer in certain situations for readability.
const something = curry <| function(state, pattern) {
// something
}
const something = function(state, pattern) {
// something
} |> curry
Because these pipe operators are incredibly useful in functional style javascript programming and for the benefit of anyone who came looking for a solution to use this right now like me, I have made a babel plugin that uses the bitwise operators << and >> which I rarely use to achieve both forward and backward piping for the time being and in the rare cases where the bitwise operators are required the use of the "no pipe"; directive will disable the plugin for a certain scope.
https://github.com/michaelmitchell/babel-plugin-pipe-composition
This github repo and my favorite issue within it ;) discuss just that.
The proposal has been moving around in a small neighborhood of ideas for some months, but centers around using -> and :: as sugar around Function.prototype.apply and Function.prototype.bind, very roughly.
The current draft is for :: to sit between a scope and function (instance::method) and act much like instance.method.bind(instance), locking the this scope of the function for any calls. Alongside that, -> may be defined to pass a scope and array of arguments (as apply), so instance->method(foo, bar) would desugar to instance.method.apply(instance, [foo, bar]). At least, that's one of the directions that is being discussed (disclosure: that's my take on it).
I usually just go like this:
var something = [somedata].map(func1).map(func2).map(func3).map(func4)[0];
It solves one of the main points in the question
One of the great advantages to my mind of the common idiom of method chaining in javascript is that it reads top-to-bottom, left-to-right:` by taking advantage of how this already exists for arrays.
because this feature already exists for arrays, and converting something into an array, and back again is easy enough.
the above uses the names in the question, but here is a concrete example:
console.log([9.3].map(Math.floor).map(Math.sqrt)[0]);
// or even this:
[9.3].map(Math.floor).map(Math.sqrt).forEach(x => console.log(x));

Categories

Resources