📜 ⬆️ ⬇️

Operators?. ?? and |>: future JavaScript features you'll like

Justin Fuller, the author of the material, the translation of which we are publishing today, offers to consider three new features that are expected to appear in JavaScript in the foreseeable future. He will first talk about the JS development process, and then provide an overview of these features and show examples of their use. ?. about operators ?. ?? and |> .

About ECMAScript and JavaScript Development


If you are already familiar with the features of the work of the ECMA TC39 working group, and how it performs the selection and processing of suggestions for improving JavaScript, you may well skip this section. If you are one of those who are interested to find out about it - here is a brief overview of what the TC39 does.

JavaScript is an implementation of a standard called ECMAScript , which was created to standardize language implementations that appeared in the early years of web browsers.
')
There are eight editions of the ECMAScript standard and seven releases (the fourth edition of the standard did not come out, after the third one there is the fifth one). The developers of JavaScript-engines begin to implement innovations of the language after the release of the standard. Here you can see that not every engine realizes all the possibilities, while some engines take more time to introduce innovations than others. Although this state of affairs is not ideal, it’s still better than a complete lack of standards.

suggestions


New features of the language are initially the so-called sentences, which, before being included in the standard, undergo a harmonization procedure. If the proposal is deemed useful and backward compatible with all that already exists, it will be included in the next edition of the standard.

The proposal review process consists of five steps described in this document . At the very beginning of the proposal is in a state of draft (strawman), this is the same as Stage 0 . At this step, the proposal has either not yet been submitted to the technical committee, or it has not yet been rejected, but it does not yet meet the criteria for moving on to the next stage of approval. Those opportunities, which we will discuss below, have already passed Stage 0.

I would like to recommend readers to avoid using JS innovations in production, proposals describing which are at the stage of Stage 0. It is better to wait for them to move to more stable stages of agreement. The purpose of this recommendation is to help you avoid problems in the event that the proposal is rejected or is very much changed.

Testing system


Materials in which they talk about new features of programming languages ​​often contain code fragments taken out of context. Sometimes these features are used to create some learning applications. However, we will not do either one or the other here. Since I am a big fan of TDD , I believe that the best way to learn some new technology is to test it.

We will use here to master the JS features described, what Jim Newkirk calls learning tests . Such tests are not based on statements about code written in a certain language. They are built on the analysis of statements concerning the language itself. The same approach may be useful when learning the API of third-party developers, and when mastering any possibility of a language.

Transporters


If you are not familiar with transpilers, then you may have a question about how we are going to use features of the language that are not yet implemented. It should be noted here that JavaScript is constantly evolving, and it takes time to implement its new features, which are interesting for programmers, in common engines. As a result, in the JS ecosystem there is such a thing as transpilers .

They allow you to convert the code written in JS using the latest features, which, for example, are not yet included in the standards and are not implemented by popular engines, into JS code that is understood by the existing JavaScript program execution environments. This will allow, for example, to use in the code even sentences of the Stage 0 level, and what happens after processing the code with the transpiler can be done, for example, in modern browsers or in the Node.js environment. This is done by converting the new code in such a way that it, for the runtime environment, looks like code written on one of the JS versions it supports.

One of the most popular JavaScript transpilers is Babel , very soon we will talk about how to use it.

Work environment preparation


If you want to repeat on your own everything that we will talk about - you can do this by setting up an npm project and installing the necessary dependencies. It is assumed that you now have Node.js and NPM installed.

In order to prepare for our experiments, run the following command, being in the directory designated for these experiments:

 npm init -f && npm i ava@1.0.0-beta.3 @babel/preset-env@7.0.0-beta.42 @babel/preset-stage-0@7.0.0-beta.42 @babel/register@7.0.0-beta.42 @babel/polyfill@7.0.0-beta.42 @babel/plugin-transform-runtime@7.0.0-beta.42 @babel/runtime@7.0.0-beta.42 --save-dev 

Then add the following to the package.json file:

 "scripts": { "test": "ava" }, "ava": {    "require": [        "@babel/register",   "@babel/polyfill"   ] } 

Next, create a .babelrc file with the following contents:

 {  "presets": [      ["@babel/preset-env", {          "targets": {              "node": "current"          }   }],     "@babel/preset-stage-0"  ],  "plugins": [      "@babel/plugin-transform-runtime" ] } 

Now you are ready to write tests that explore new JS features.

1. Operator?


In the course of writing applications in JavaScript, we are constantly working with objects. However, sometimes these objects do not have the structure that we expect from them. For example, an object with data. Such an object can be obtained, for example, as a result of a database query or when accessing a certain API.

 const data = { user: {   address: {     street: 'Pennsylvania Avenue',   }, }, }; 

This object describes a registered user. And here is a similar object, but in this case the user whom he describes did not complete the registration.

 const data = { user: {}, }; 

If you try to access the street property of the address object nested in this “incomplete” object, expecting that it will look the same as an object containing all the necessary data, you may encounter the following error:

 console.log(data.user.address.street); // Uncaught TypeError: Cannot read property 'street' of undefined 

In order to avoid this, in the current conditions, it is necessary to use the following construction to refer to the street property:

 const street = data && data.user && data.user.address && data.user.address.street; console.log(street); // undefined 

I believe that all this, firstly, looks bad, secondly, it is hard to write, and thirdly, such constructions turn out to be too long.

It is in such situations that the optional sequences or optional chains, represented by the operator, looking like a question mark with a dot ( ?. ), Can by the way turn out to be.

 console.log(data.user?.address?.street); // undefined 

It looks better, and it is easier to build such structures. I am sure you will agree with this statement. Convinced of the usefulness of this new opportunity, explore it. Let's write a test by putting the code in the file optional-chaining.test.js . We, in this section, will gradually supplement this file with new tests.

 import test from 'ava'; const valid = { user: {   address: {     street: 'main street',   }, }, }; function getAddress(data) { return data?.user?.address?.street; } test('Optional Chaining returns real values', (t) => { const result = getAddress(valid); t.is(result, 'main street'); }); 

This test allows us to verify that the operator ?. , in the event that an object looks like it is expected, it works in the same way as the way to access the properties of an object through a point. Now let's check the behavior of this operator in a situation where the object is not what we consider it.

 test('Optional chaining returns undefined for nullish properties.', (t) => { t.is(getAddress(), undefined); t.is(getAddress(null), undefined); t.is(getAddress({}), undefined); }); 

And this is how optional sequences work when they are used to access array elements:

 const valid = { user: {   address: {     street: 'main street',     neighbors: [       'john doe',       'jane doe',     ],   }, }, }; function getNeighbor(data, number) { return data?.user?.address?.neighbors?.[number]; } test('Optional chaining works for array properties', (t) => { t.is(getNeighbor(valid, 0), 'john doe'); }); test('Optional chaining returns undefined for invalid array properties', (t) => { t.is(getNeighbor({}, 0), undefined); }); 

Sometimes it happens that we do not know whether any function is implemented in the object. For example, this situation is common when working with browsers. Outdated browsers may not contain implementations of certain functions. Thanks to the operator ?. we can find out whether a feature of interest is implemented in an object or not. Here's how to do it:

 const data = { user: {   address: {     street: 'main street',     neighbors: [       'john doe',       'jane doe',     ],   },   getNeighbors() {     return data.user.address.neighbors;   } }, }; function getNeighbors(data) { return data?.user?.getNeighbors?.(); } test('Optional chaining also works with functions', (t) => { const neighbors = getNeighbors(data); t.is(neighbors.length, 2); t.is(neighbors[0], 'john doe'); }); test('Optional chaining returns undefined if a function does not exist', (t) => { const neighbors = getNeighbors({}); t.is(neighbors, undefined); }); 

Expressions will not be executed if something is wrong in the chain. If we talk about the internal implementation of this mechanism, then our expression is converted approximately to the following:

 value == null ? value[some expression here]: undefined; 

As a result, after the operator ?. nothing will be executed if the value is represented as undefined or null . You can look at this rule in action using the following test:

 let neighborCount = 0; function getNextNeighbor(neighbors) { return neighbors?.[++neighborCount]; } test('It short circuits expressions', (t) => { const neighbors = getNeighbors(data); t.is(getNextNeighbor(neighbors), 'jane doe'); t.is(getNextNeighbor(undefined), undefined); t.is(neighborCount, 1); }); 

As you can see, optional sequences can reduce the need for if constructs, in third-party libraries like lodash , and in using clumsy constructs that use && .

â–ŤOperator? .. and performance


You probably noticed that using optional sequences means an additional load on the system. The thing is that every time an operator is used ?. , the system is forced to conduct additional checks. Operator Abuse ?. can significantly affect the performance of programs.

I would advise you to use this feature together with some kind of verification system that allows you to analyze objects when they are received from somewhere or when they are created. Does this reduce the need to use the design ?. and limit its impact on performance.

2. Operator ??


Here are some common operations that can be encountered while working with JavaScript. Usually they look like a single expression, the meaning of which is as follows:

  1. Checking the value for undefined and null .
  2. Specifies the default value.
  3. Ensuring that the appearance of the values 0 , false , and '' does not result in the use of the default value.

Here is an example of a similar expression:

 value != null ? value : 'default value'; 

You can meet and illiterately written version of this expression:

 value || 'default value' 

The problem with the second example is that the third item in the above list is not fulfilled here. The appearance of the number 0 here, the value false or the empty string will be recognized as false , and this is not what we need. That is why the test for null and undefined should be carried out explicitly:

 value != null 

This expression is similar to this:

 value !== null && value !== undefined 

In such situations, the new operator, called “union with null value” ( nullish coalescence ), which looks like two question marks ( ?? ), will be very useful. In this situation, you can now use the following design:

 value ?? 'default value'; 

This protects us from accidentally using default values ​​when values ​​that are recognized as false appear in expressions, but it allows us to detect null and undefined values ​​without resorting to the ternary operator and checking the form != null .

Now, having become acquainted with this operator, we can write tests in order to test it in action. These tests will be nullish-coalescing.test.js in the file nullish-coalescing.test.js .

 import test from 'ava'; test('Nullish coalescing defaults null', (t) => { t.is(null ?? 'default', 'default'); }); test('Nullish coalescing defaults undefined', (t) => { t.is(undefined ?? 'default', 'default'); }); test('Nullish coalescing defaults void 0', (t) => { t.is(void 0 ?? 'default', 'default'); }); test('Nullish coalescing does not default 0', (t) => { t.is(0 ?? 'default', 0); }); test('Nullish coalescing does not default empty strings', (t) => { t.is('' ?? 'default', ''); }); test('Nullish coalescing does not default false', (t) => { t.is(false ?? 'default', false); }); 

From these tests it can be understood that the default values ​​are used for null , undefined and void 0 (it is converted to undefined ). In this case, the default values ​​do not apply in cases where values ​​appear in the expression that are perceived as false, such as 0 , an empty string, and false .

3. Operator |>


In functional programming, there is such a thing as composition . This is an action representing a chaining of several function calls. Each function accepts, as input, the output of the previous function. Here is an example of the composition prepared by means of ordinary JavaScript:

 function doubleSay (str) { return str + ", " + str; } function capitalize (str) { return str[0].toUpperCase() + str.substring(1); } function exclaim (str) { return str + '!'; } let result = exclaim(capitalize(doubleSay("hello"))); result //=> "Hello, hello!" 

This is so common that function composition facilities exist in a variety of libraries that support functional programming, for example, such as lodash and ramda .

Thanks to the new pipeline operator , which looks like a combination of a vertical bar and a “more” ( |>) sign, you can stop using third-party libraries and rewrite the above example as follows:

 let result = "hello" |> doubleSay |> capitalize |> exclaim; result //=> "Hello, hello!" 

The purpose of this statement is to improve the readability of chains of function calls. In addition, in the future, this operator will be used in the construction of partial application of functions. Now it can be done as follows:

 let result = 1 |> (_ => Math.max(0, _)); result //=> 1 let result = -5 |> (_ => Math.max(0, _)); result //=> 0 

Having dealt with the basics, we will write the tests, placing them in the file pipeline-operator.test.js :

 import test from 'ava'; function doubleSay (str) { return str + ", " + str; } function capitalize (str) { return str[0].toUpperCase() + str.substring(1); } function exclaim (str) { return str + '!'; } test('Simple pipeline usage', (t) => { let result = "hello"   |> doubleSay   |> capitalize   |> exclaim; t.is(result, 'Hello, hello!'); }); test('Partial application pipeline', (t) => { let result = -5   |> (_ => Math.max(0, _)); t.is(result, 0); }); test('Async pipeline', async (t) => { const asyncAdd = (number) => Promise.resolve(number + 5); const subtractOne = (num1) => num1 - 1; const result = 10   |> asyncAdd   |> (async (num) => subtractOne(await num)); t.is(await result, 14); }); 

Analyzing these tests, you can see that when using an asynchronous function declared in the pipeline using the async , you need to wait for the value to appear using the await keyword. The point here is that the value becomes a Promise object. There are several proposals for changes aimed at supporting structures of the |> await asyncFunction , but they have not yet been implemented and the decision on their future fate has not yet been made.

Results


We hope you enjoyed the expected JS features to which this material is dedicated. Here is a repository with tests that we dealt with in this material. But for convenience, there are links to the innovations discussed here: the operator? operator ?? operator |> .

Dear readers! How do you feel about the emergence of new features of JS, which we talked about?

Source: https://habr.com/ru/post/353446/


All Articles