Operators?., ?? and |>: future JavaScript features that you like

Justin Fuller, the author of the material, whose translation we are publishing today, proposes to consider three new features, the appearance of which is expected in jаvascript in the foreseeable future. First he will talk about the development process of JS, and after that will present an overview of these features and will show examples of their use. We will talk about operators ?. , ?? and |> .
 
 

About the ECMAScript standard and the development of jаvascript


 
Operators?., ?? and |>: future JavaScript features that you like If you are already familiar with the features of the work of the working group ECMA TC39 , with how it selects and processes suggestions for improving jаvascript, you can easily skip this section. If you are one of those who are interested in learning about this - here is a brief overview of what the TC39 is doing.
 
 
jаvascript is the implementation of a standard called ECMAScript , which was created for the standardization of realizations language, which appeared in the early years of the existence of web browsers.
 
 
There are eight revisions ECMAScript standard and seven releases (the fourth edition of the standard did not come out, after the third one there is a fifth one). Developers jаvascript-engines begin to implement the innovation of the language after the release of the standard. Here you can see that not every engine implements all the features, while some engines require more time to innovate than others. Although this state of affairs is not ideal, it is, nevertheless, better than a complete lack of standards.
 
this document . At the very beginning, the proposal is in the draft state (strawman), this is the same as Stage 0 . At this stage, the proposal is either not yet submitted to the technical committee, or it has not been rejected yet, but does not yet meet the criteria that allow it to proceed to the next stage of the agreement. Those possibilities, which we will talk about below, have already passed Stage 0.
 
 
I would like to recommend readers to avoid using JS innovations in production, the proposals describing which are at Stage 0. It is better to wait for their transition to more stable stages of agreement. The purpose of this recommendation is to help you avoid problems if the proposal is rejected or very changed.
 
 

Testing system


 
Materials that talk about the new features of programming languages ​​often contain code snippets, taken out of context. Sometimes these features are used to create some training applications. However, neither of which we will do here. Since I'm 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 develop the described features of JS, what Jim Newkirk calls training 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 can be useful in the study of third-party APIs, and in the development of any language capability.
 
 

Transliters


 
If you are not familiar with transpilators, then you may have a question about how we are going to use the language features that are not yet implemented. Here it should be noted that jаvascript is constantly evolving, and for implementation in the popular engines of its new features, interesting programmers, it takes time. As a result, in the JS ecosystem there is such a thing as transpalators .
 
 
They allow you to convert 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 understands the existing jаvascript execution environment. This will allow, for example, to use even in the code of the sentence level of Stage ? and what happens after the code is processed by the translator, can be executed, for example, in modern browsers or in the Node.js. This is done by transformations new code in such a way that, for the runtime, it looks like code written on one of the supported versions of JS.
 
 
One of the most popular jаvascript-transpilers is Babel , very soon we will talk about how to use it.
 
 

Preparation of the working environment


 
If you want to repeat everything yourself about what we are talking about, you can do it by configuring the npm project and setting the necessary dependencies. It is assumed that now you have already installed Node.js and NPM .
 
 
In order to prepare for our experiments, execute the following command, being in the directory specified for these experiments:
 
 
    npm init -f && npm i [email protected]???-beta.3 @ babel /preset-env @ ???-beta.42 @ babel /preset-stage-0 @ ???-beta.42 @ babel /register @ ???-beta.42 @ babel /polyfill @ ???-beta.42 @ babel /plugin-transform-runtime @ ???-beta.42 @ babel /runtime @ ???-beta.42 - save-dev    

 
Then add the following to the file. package.json :
 
 
    "scripts": {
"test": "ava"
},
"ava": {
"require":[     
   "@babel/register",
   "@babel/polyfill"   
 ]
}

 
Next, create the file. .babelrc 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 the new features of JS.
 
 

1. The operator?.


 
In the process of writing applications in jаvascript, we constantly work with objects. However, sometimes these objects do not have the structure that we expect from them. For example, here is an object with data. A similar object can be obtained, say, as a result of a query to a database or by accessing an API.
 
 
    const data = {
user: {
address: {
street: 'Pennsylvania Avenue',
},
},
};};

 
This object describes the user who passed the registration. 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 property street object address , enclosed in this "incomplete" object, expecting that it will look the same as an object containing all the necessary data, you can encounter such an error:
 
 
    console.log (data.user.address.street); //Uncaught TypeError: Can not read property 'street' of undefined    

 
In order to avoid this, in the current conditions it is necessary to use, to access property street , this construction:
 
 
    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's hard to write, and thirdly - these constructions turn out to be too long.
 
 
It is in such situations that the optional sequences or optional chains (
? optional chaining ), Represented by an operator that looks like a question mark with a dot ( ? . ) Turn out to be exactly in such situations.
 
 
    console.log (data.user? .address? .street); //undefined    

 
It looks better, and it's easier to build such constructions. I'm sure you will agree with this statement. Having convinced ourselves of the usefulness of this new opportunity, we will investigate it. We will write the 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 the object looks like it expects, it works just like the way to access object properties via a period. Now let's check the behavior of this operator in a situation where the object is not what we think it is.
 
 
    test ('Optional chaining returns undefined for nullish properties.', (t) => {
t.is (getAddress (), undefined);
t.is (getAddress (null), undefined);
is (getAddress ({}), undefined);
});

 
And here's how the 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 if a feature is implemented in the object. For example, this situation is common when working with browsers. Obsolete browsers may not contain implementations of certain functions. Thanks to the operator ?. we can find out whether the function we are interested in is implemented in the 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, our expression is transformed into approximately the following:
 
 
    value == null? value[some expression here]: undefined;    

 
As a result, after the operator ?. Nothing will be done 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 designs. if , in third-party libraries like lodash , and in the use of clumsy structures in which is used. && .
 
 

▍Operator? andProductivity


 
You probably noticed that using optional sequences means an additional load on the system. The thing is that every time the operator is used. ?. , the system is forced to conduct additional checks. Abuse of the operator ?. can significantly affect the performance of programs.
 
 
I would advise you to use this feature along with some sort of verification system that allows you to analyze objects when they are received from somewhere or when they are created. This will reduce the need for the design of ?. and will limit its impact on performance.
 
 

2. The operator?


 
Here are some common operations that you can encounter while working with jаvascript. Usually they look like a single expression, the meaning of which is as follows:
 
 
  1.  
  2. Check the value at undefined and null .  
  3. Specify the default value.  
  4. Ensuring that the appearance of the values ​​ 0 , false , and '' does not lead to the use of the default value.  

 
Here is an example of such an expression:
 
 
    value! = null? value: 'default value';    

 
One can also find an illiterate written version of this expression:
 
 
    value || 'default value'    

 
The problem of the second example is that the third item of the list above is not executed here. The appearance of the number 0 , the values ​​of false or an empty string will be recognized as the value false , and this is not what we need. That's why the test on null and undefined should be carried out in an explicit form:
 
 
    value! = null    

 
This expression is similar to this:
 
 
    value! == null && value! == undefined    

 
In such situations, a new operator, called "union with the value ? will come in very handy. null "(
? nullish coalescence ), Which looks like two question marks ( ?? ). In this situation, you can now use the following design:
 
 
    value ?? 'default value';    

 
This protects us from accidentally using the default value when expressions appear in values ​​that are recognized as false , but at the same time it makes it possible to detect the values ​​of null and undefined , without resorting to a ternary operator and to checking the form ! = null .
 
 
Now, having got acquainted with this operator, we can write tests in order to check it in the case. These tests will be placed 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, you can understand that the default values ​​are used for null , undefined and void 0 (it is converted to undefined ). In this case, the default values ​​are not applied in those cases when the values ​​appearing in the expression, perceived as false, like 0 , an empty string and false .
 
 

3. The operator |>


 
In functional programming there is such a thing as composition . This is an action that is a chaining of several function calls. Each function accepts, as input, the output of the previous function. Here is an example of a composition prepared by means of ordinary jаvascript:
 
 
    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 widespread that means for composing functions exist in a number of libraries that support functional programming, for example, in such as lodash and ramda .
 
 
Thanks to the new conveyor operator ( pipeline operator ), Which looks like a combination of a vertical line and a sign "more" ( |>) , you can abandon the use of third-party libraries and rewrite the above example as follows:
 
 
    let result = "hello"
|> doubleSay
|> capitalize
|> exclaim;
result //=> "Hello, hello!"

 
The purpose of this operator is to improve the readability of function call chains. In addition, in the future, this operator will find application in the constructions of partial application of functions. Now it can be done as follows:
 
 
    let result = 1
|> (_ => Math.max (? _));
result //=> 1
let result = -5
|> (_ => Math.max (? _));
result //=> 0

 
Having dealt with the basics, 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 (? _));
.
t.is (result, 0 );
});
test ('Async pipeline', async (t) => {
.const asyncAdd = (number) => Promise.resolve (number + 5);
const subtractOne = (num1) => num1 - 1;
result = 10
|> asyncAdd
|> (async (num) => subtractOne (await num));
.
t.is (await result, 14);
});

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


Results of


 
We hope that you liked the expected features of JS, which this material is dedicated to. Here is repository with the tests that we were doing in this material. And here, for convenience - links to the novelties discussed here: operator?. , operator ?? , operator |> .
 
 
Dear readers! How do you feel about the emergence of new JS features, which we told about?
 
 
+ 0 -

Add comment