ES2020 innovations that I really like

JavaScript has been developing very fast in recent years. This is especially true for the period following the release of the ES6 standard in 2015. Since then, a lot of great features have appeared in the language. Much new has been proposed for inclusion in the ES2020 standard. A final list of features has already been formed, the appearance of which can be expected in the standard after its approval. This is good news for all JS enthusiasts. The author of the article, the translation of which we are publishing today, says that among these opportunities there are those with which he is especially pleased. Before they appeared, it was much harder for him to write code in situations in which they are applicable. According to him, if they had appeared in the language earlier, they would have saved him a lot of time and effort.





Optional chains


Optional Chaining is, for me personally, one of the most exciting features of the ES2020 standard. I have written many programs in which this feature would be extremely useful. 

Optional chains allow you to organize secure access to deeply embedded properties of objects without the need to verify the existence of each of them. Take a look at how this feature works.

First, look at the code that had to be written before the advent of optional chains.

â–Ť Code before the appearance of optional chains


const user = {
   firstName:"Joseph",
   lastName:"Kuruvilla",
   age:38,
   address:{
      number:"239",
      street:"Ludwig Lane",
      city:"Chennai",
      zip:"600028",
   prop1:{
    prop2:{
     prop3:{
      prop4:{
       value:'sample'
      }
     }
    }
   }
   }
}
if(user && user.address){
 console.log(user.address.zip);
 //600028
}
if(user && user.address && user.address.prop1 && user.address.prop1.prop2 && user.address.prop1.prop2.prop3 && user.address.prop1.prop2.prop3.prop4){
 console.log(user.address.prop1.prop2.prop3.prop4.value);
 //sample
}
//    
console.log(user.address.prop102.po);
//Error

As you can see, in order to avoid the occurrence of errors, it seems Cannot read property 'po' of undefined, it is necessary, at each level of nesting, to check the properties for their existence. With an increase in the depth of nesting of entities, the number of checked properties also increases. This means that the programmer himself has to write code that protects it from properties that, when accessed, can run into the values ​​of nullor undefined.

â–Ť Code after the appearance of optional chains


Writing code like the one we just reviewed made it much easier with the advent of optional chains. To organize safe work with deeply embedded properties of objects, it is enough to use the operator ?.. It saves us from the need to independently check the values ​​on nulland undefined.

Here's what it looks like:

const user = {
   firstName:"Joseph",
   lastName:"Kuruvilla",
   age:38,
   address:{
      number:"239",
      street:"Ludwig Lane",
      city:"Chennai",
      zip:"600028",
   prop1:{
    prop2:{
     prop3:{
      prop4:{
       value:'sample'
      }
     }
    }
   }
   }
}
console.log(user?.address?.zip);
//600028
console.log(user?.address?.prop1?.prop2?.prop3?.prop4?.value);
//sample
//    
console.log(user?.address?.prop102?.po);
//undefined

Well, isn’t it fine? Thanks to this innovation, ES2020 made it possible to get rid of a huge number of lines of code.

Validating only null and undefined values


Checking values ​​only on nulland undefined(Nullish Coalescing) is one of those features that really delighted me even when the possibilities were at the proposal stage. I often came across the need to write specialized functions to perform the appropriate checks.

JavaScript is known to have “false” and “true” meanings. Now we can say that “zero” values ​​were added to them. These values ​​include nulland undefined. With JavaScript terms of "false" are empty strings, the number 0, the values undefined, null, false, NaN. That is, for example, a certain expression for checking the value for “falsehood” will work on an empty string, and on the valueundefined, and much more on what. And the expression for checking the value of whether it is “zero” will return trueonly for nulland undefined. Maybe this opportunity doesn’t seem so wonderful to you personally, but, in fact, it is very, very important.

Let's look at some examples.

▍ The code until the ability to check values ​​only on null and undefined


Recently, I was working on a project in which I needed to implement the functionality of switching between light and dark themes. At the same time, I had to check the state of the control, find out if it corresponds to the value of trueor false. If the user did not set any value, by default, it should be equal true. Here's how I solved this problem before the possibility of checking values ​​only on nulland undefined:

const darkModePreference1 = true
const darkModePreference2 = false
const darkModePreference3 = undefined
const darkModePreference4 = null
const getUserDarkModePreference = (darkModePreference) => {
  if (darkModePreference || darkModePreference === false) {
    return darkModePreference
  }
  return true
}
getUserDarkModePreference(darkModePreference1) 
// true
getUserDarkModePreference(darkModePreference2) 
// false
getUserDarkModePreference(darkModePreference3) 
// true
getUserDarkModePreference(darkModePreference4) 
// true

▍ Code after the possibility of checking values ​​only on null and undefined


Once this feature has appeared in the language for checking nulland undefinedsufficient operator ??. You can do without a conditional statement if:

const darkModePreference1 = true
const darkModePreference2 = false
const darkModePreference3 = undefined
const darkModePreference4 = null
const getUserDarkModePreference = (darkModePreference) => {
  return darkModePreference ?? true;
}
getUserDarkModePreference(darkModePreference1) 
// true
getUserDarkModePreference(darkModePreference2) 
// false
getUserDarkModePreference(darkModePreference3) 
// true
getUserDarkModePreference(darkModePreference4) 
// true

Here the following happens: if the variable darkModePreferencecontains a “zero” value, then the value is returned true. This makes it easier to write code, it is compact and easy to understand.

Dynamic imports


Dynamic Imports contribute to more efficient application performance. This technology allows you to import JS files dynamically, in the form of modules, without involving additional tools. Moreover, if the module is not needed, then it is not imported. Prior to the ES2020, modules were imported regardless of whether they are used by the application or not.

For example, suppose we need to implement the functionality of loading a certain file in PDF format.

Consider, as usual, the old and new options for solving this problem.

â–Ť Code before support for dynamic imports


Based on the real situation, we can expect that the ability to download materials in PDF format will not be used by all visitors to the page. But the corresponding module, anyway, needs to be imported into the code. This means that the module, whether it is needed or not, will be loaded when the page loads.

import { exportAsPdf } from './export-as-pdf.js'
const exportPdfButton = document.querySelector('.exportPdfBtn');
exportPdfButton.addEventListener('click', exportAsPdf);

This creates an additional load on the system, which can be facilitated by using the lazy loading of modules. This can be done using code separation technology, which is available in webpack or in other module loaders (and their use in itself means spending a certain amount of system resources).

But now, thanks to the ES2020, we have a standard way to dynamically load modules, which allows us to do without bundlers.

â–Ť Code after the appearance of support for dynamic imports


const exportPdfButton = document.querySelector('.exportPdfBtn');
exportPdfButton.addEventListener('click', () => {
  import('./export-as-pdf.js')
    .then(module => {
      module.exportAsPdf()
    })
    .catch(err => {
      //      -  
    })
})

As you can see, now you can organize lazy loading of modules by loading only when the appropriate module is needed. This reduces system load and increases page loading speed.

Construct Promise.allSettled


If you need to perform some action only if all the promises have been successfully resolved, you can use the method Promise.all(). True, this method has one drawback. The method will throw an error if at least one promise passed to it is rejected. This means that the necessary action will not be performed until all promises are successfully resolved.

You may not need this. Perhaps the following scenario will suit you: “The result is not important to me. I need the code to be executed after the completion of all the promises. ” In this case, the method is useful to you Promise.allSettled(). The corresponding promise is successfully resolved only after the completion of other promises. It doesn’t matter if they have worked successfully or unsuccessfully.

â–Ť Code using the Promise.all construct


const PromiseArray = [
    Promise.resolve(100),
    Promise.reject(null),
    Promise.resolve("Data release"),
    Promise.reject(new Error('Something went wrong'))];
Promise.all(PromiseArray)
  .then(data => console.log('all resolved! here are the resolve values:', data))
  .catch(err => console.log('got rejected! reason:', err))
//got rejected! reason: null

Apparently, it Promise.allgives an error after the rejection of one of the promises transferred to it.

â–Ť Code that uses the Promise.allSettled construct


const PromiseArray = [
    Promise.resolve(100),
    Promise.reject(null),
    Promise.resolve("Data release"),
    Promise.reject(new Error('Something went wrong'))];
Promise.allSettled(PromiseArray).then(res =>{
console.log(res);
}).catch(err => console.log(err));
//[
//{status: "fulfilled", value: 100},
//{status: "rejected", reason: null},
//{status: "fulfilled", value: "Data release"},
//{status: "rejected", reason: Error: Something went wrong ...}
//]

And here, although some of the promises are rejected, it Promise.allSettledreturns the results issued by all the promises transferred to it.

Other notable features


â–Ť BigInt data type


The new data type BigIntallows you to work with numbers whose length exceeds the length of numbers with which you could work in JavaScript before it appeared ( pow(2,53)-1). True, this data type is not backward compatible with what was in the language before. The IEEE 754 standard, which is the basis for working with numbers in JavaScript, does not support the numbers that are possible to work withBigInt

â–Ť String.prototype.matchAll method


The method String.prototype.matchAll()is related to regular expressions. It returns an iterator, allowing you to work with all matches found in a string using a regular expression , including groups .

â–Ť Global property globalThis


The global property globalThisholds a reference to the global object corresponding to the environment in which the code is executed. In the browser, the global object is represented by the object window. In Node.js, this is an object global. In web workers, this is an object self.

What innovations do you like most about the ES2020?


All Articles