JavaScript innovations: Google I / O 2019 results. Part 2

Today we are publishing the second part of the translation of JavaScript innovations. Here we talk about separators of digits of numbers, about BigInt-numbers, about working with arrays and objects, about globalThis , about sorting, about the internationalization API and about promises.

The first part

Number Separators

Long numbers found in programs are hard to read. For example, 1000000000 is one billion in decimal. But at a glance it’s hard to understand. Therefore, if the reader of the program encounters something similar - he, in order to correctly perceive it, will have to carefully consider zeros.

In modern JavaScript, you can use the separator of digits of numbers - the underscore ( _ ), the use of which improves the readability of long numbers. Here's how numbers written using a delimiter look in code:

 var billion = 1_000_000_000; console.log( billion ); // 1000000000 

Separators can be used for arbitrary division of numbers into fragments. JavaScript, when dealing with numbers, simply ignores separators. They can be used when writing any numbers: integers, floating point, binary, hexadecimal, octal.

 console.log( 1_000_000_000.11 ); // 1000000000.11 console.log( 1_000_000_000.1_012 ); // 1000000000.1012 console.log( 0xFF_00_FF ); // 16711935 console.log( 0b1001_0011 ); // 147 console.log( 0o11_17 ); // 591 

→ Support

Bigint data type

Numbers in JavaScript are created using the Number constructor function.

The maximum value that can be safely represented using the Number data type is (2⁵³ - 1), that is, 9007199254740991. You can see this number using the Number.MAX_SAFE_INTEGER construct.

Please note that when a numeric literal is used in JS code, JavaScript processes it, creating an object based on it using the Number constructor. The prototype of this object contains methods for working with numbers. This happens with all primitive data types .

What will happen if we try to add something to the number 9007199254740991?

 console.log( Number.MAX_SAFE_INTEGER ); // 9007199254740991 console.log( Number.MAX_SAFE_INTEGER + 10 ); // 9007199254741000 

The result of adding Number.MAX_SAFE_INTEGER and 10, the second output of console.log() , is incorrect. This is due to the fact that JS cannot correctly perform calculations with numbers greater than the value of Number.MAX_SAFE_INTEGER . You can deal with this problem by using the bigint data bigint .

The bigint type allows bigint to represent integers that are greater than Number.MAX_SAFE_INTEGER . Working with BigInt values ​​is similar to working with values ​​of type Number . In particular, the language has the function BigInt() , with which you can create the corresponding values, and the built-in primitive data type bigint , used to represent large integers.

 var large = BigInt( 9007199254740991 ); console.log( large ); // 9007199254740991n console.log( typeof large ); // bigint 

JavaScript adds n to the end of BigInt literals. For us, this means that such literals can be written by adding n to the end of integers.

Now that we have BigInt numbers at our disposal, we can safely perform mathematical operations on large numbers of type bigint .

 var large = 9007199254740991n; console.log( large + 10n ); // 9007199254741001n 

A number of type number is not the same as a number of type bigint . In particular, we are talking about the fact that BigInt-numbers can only be integers. As a result, it turns out that arithmetic operations that use the bigint and number types cannot be performed.

It should be noted that the BigInt() function can take various numbers: decimal, binary, hexadecimal, octal. Inside this function, they will be converted to numbers, the decimal number system is used to represent them.

The bigint type also supports bit separators:

 var large = 9_007_199_254_741_001n; console.log( large ); // 9007199254741001n 

→ Support

New Array Methods: .flat () and .flatMap ()

Here we’ll talk about the new prototype methods for the Array object — the .flat() and .flatMap() methods.

▍ .flat () method

Now objects of type Array have a new method - .flat(n) . It returns a new array, allowing recursively to raise the elements of arrays to the specified level n . By default, n is 1. This method can be passed n equal to Infinity , which allows you to convert an array with nested arrays into a one-dimensional array.

 var nums = [1, [2, [3, [4, 5]]]]; console.log( nums.flat() ); // [1, 2, [3, [4,5]]] console.log( nums.flat(2) ); // [1, 2, 3, [4,5]] console.log( nums.flat(Infinity) ); // [1, 2, 3, 4, 5] 

→ Support

▍ .flatMap () method

When solving everyday tasks, the programmer may sometimes need to process the array using the .map() method with its subsequent transformation into a flat structure. For example, create an array containing the numbers and squares of these numbers:

 var nums = [1, 2, 3]; var squares = n => [ n, n*n ] ) console.log( squares ); // [[1,1],[2,4],[3,9]] console.log( squares.flat() ); // [1, 1, 2, 4, 3, 9] 

The solution to this problem can be simplified by using the .flatMap() method. It converts the arrays returned by the callback function passed to it in the same way that it would convert their .flat() method with parameter n equal to 1.

 var nums = [1, 2, 3]; var makeSquare = n => [ n, n*n ]; console.log( nums.flatMap( makeSquare ) ); // [1, 1, 2, 4, 3, 9] 

→ Support

▍ Object.fromEntries () Method

You can extract : type pairs from an object : can be used using the static Object method, which returns an array, each element of which is an array containing, as the first element, a key, and as the second - a value.

 var obj = { x: 1, y: 2, z: 3 }; var objEntries = Object.entries( obj ); console.log( objEntries ); // [["x", 1],["y", 2],["z", 3]] 

Now we have at our disposal a static method Object.fromEntries() , which allows us to convert a similar structure back into an object.

 var entries = [["x", 1],["y", 2],["z", 3]]; var obj = Object.fromEntries( entries ); console.log( obj ); // {x: 1, y: 2, z: 3} 

The entries() method was used to facilitate filtering and mapping of data stored in objects. The result is an array. But so far, the task of converting such an array into an object has not had a beautiful solution. It is for solving this problem that you can use the Object.fromEntries() method.

 var obj = { x: 1, y: 2, z: 3 }; // [["x", 1],["y", 2],["z", 3]] var objEntries = Object.entries( obj ); // [["x", 1],["z", 3]] var filtered = objEntries.filter( ( [key, value] ) => value % 2 !== 0 //  ,     ); console.log( Object.fromEntries( filtered ) ); // {x: 1, z: 3} 

If the Map : data structure is used to store the pairs : , then the data in it is stored in the order they were added to it. At the same time, how the data is stored resembles the array returned by the Object.entries() method. The Object.fromEntries() method is Object.fromEntries() easy to use for transforming Map data structures into objects.

 var m = new Map([["x", 1],["y", 2],["z", 3]]); console.log( m ); // {"x" => 1, "y" => 2, "z" => 3} console.log( Object.fromEntries( m ) ); // {x: 1, y: 2, z: 3} 

→ Support

▍ Global property globalThis

We are familiar with the this used in JavaScript. It does not have some fixed value. Instead, the meaning of this depends on the context in which it is accessed. In any environment, the this points to a global object when it is accessed from the context of the highest level. This is the global meaning of this .

In browser-based JavaScript, for example, the global value for this is the window object. You can verify this by using the console.log(this) construct at the top level of the JavaScript file (in the most external context) or in the browser JS console.

Accessing this in the browser console

The global value of this in Node.js points to a global object. Inside a web worker, it points to the worker himself. However, getting the global this value is not an easy task. The fact of the matter is that you cannot access this anywhere. For example, if you try to do this in the constructor of the class, it turns out that this points to an instance of the corresponding class.

In some environments, you can use the self keyword to access the global value of this . This keyword plays the same role as the mechanisms for accessing this value in browsers, in Node.js, and in web workers. Using knowledge of how the global value of this is called in different environments, you can create a function that returns this value:

 const getGlobalThis = () => { if (typeof self !== 'undefined') return self; if (typeof window !== 'undefined') return window; if (typeof global !== 'undefined') return global; if (typeof this !== 'undefined') return this; throw new Error('Unable to locate global `this`'); }; var globalThis = getGlobalThis(); 

Before us is a primitive polyfill to get the global this object. Read more about this here . JavaScript now has the globalThis keyword. It provides a universal way of accessing the global value of this for different environments and does not depend on the location of the program from which it is accessed.

 var obj = { fn: function() {  console.log( 'this', this === obj ); // true  console.log( 'globalThis', globalThis === window ); // true } }; obj.fn(); 

→ Support

Stable sorting

The ECMAScript standard does not offer a specific array sorting algorithm that JavaScript engines should implement. It only describes the API used for sorting. As a result, using different JS engines, one may encounter differences in the performance of sorting operations and in the stability (stability) of sorting algorithms.

Now the standard requires that sorting arrays be stable. Details on sorting stability can be found here . The essence of this characteristic of sorting algorithms is as follows. The algorithm is stable if the sorting result, which is a modified array, contains elements with the same values ​​that were not affected by the sorting in the same order in which they were placed in the original array. Consider an example:

 var list = [  { name: 'Anna', age: 21 },  { name: 'Barbra', age: 25 },  { name: 'Zoe', age: 18 },  { name: 'Natasha', age: 25 } ]; //      age [  { name: 'Natasha', age: 25 }  { name: 'Barbra', age: 25 },  { name: 'Anna', age: 21 },  { name: 'Zoe', age: 18 }, ] 

Here, the list array containing the objects is sorted by the age field of these objects. In the list array, an object with the name property equal to Barbra is located before the object with the name property equal to Natasha . Since the age values ​​of these objects are equal, we could expect that in the sorted array these elements will retain the previous arrangement order relative to each other. However, in practice it was impossible to count on this. How exactly the sorted array will be formed depended entirely on the JS engine used.

Now all modern browsers and Node.js use a stable sorting algorithm, called when accessing the .sort() array method. This allows you to always, for the same data, get the same result:

 //    [  { name: 'Barbra', age: 25 },  { name: 'Natasha', age: 25 }  { name: 'Anna', age: 21 },  { name: 'Zoe', age: 18 }, ] 

In the past, some JS engines supported stable sorting, but only for small arrays. To increase productivity when processing large arrays, they could take advantage of faster algorithms and sacrifice sort stability.

→ Support

Internationalization API

The internationalization API is designed to organize string comparisons, to format numbers, dates, and times as is customary in various regional standards (locales). Access to this API is organized through the Intl object . This object provides constructors for creating sorter objects and objects that format data. The list of locales supported by the Intl object can be found here .

▍Intl.RelativeTimeFormat ()

In many applications, it is often necessary to output the time in a relative format. It may look like “5 minutes ago,” “yesterday,” “1 minute ago,” and so on. If the website materials are translated into different languages, you have to include all possible combinations of relative constructions describing the time in the site assembly.

JS now has the Intl.RelativeTimeFormat(locale, config) constructor , which allows you to create date and time formatting systems for different locales. In particular, we are talking about objects that have a method .format(value, unit) , which allows you to generate various relative timestamps. It looks like this:

 // español ( ) var rtfEspanol= new Intl.RelativeTimeFormat('es', {  numeric: 'auto' }); console.log( rtfEspanol.format( 5, 'day' ) ); // dentro de 5 días console.log( rtfEspanol.format( -5, 'day' ) ); // hace 5 días console.log( rtfEspanol.format( 15, 'minute' ) ); // dentro de 15 minutos 

→ Support

▍Intl.ListFormat ()

The Intl.ListFormat constructor allows Intl.ListFormat to combine list items using the words and ( ) and or ( ). When creating the corresponding object, the constructor is passed the locale and the object with the parameters. Its type parameter can be conjunction , disjunction and unit . For example, if we want to combine the elements of [apples, mangoes, bananas] using a conjunction object, we get a string of the form apples, mangoes and bananas . If you use a disjunction object, we get a string of the form apples, mangoes or bananas .

The object created by the Intl.ListFormat constructor has a .format(list) method that combines lists. Consider an example:

 // español ( ) var lfEspanol = new Intl.ListFormat('es', {  type: 'disjunction' }); var list = [ 'manzanas', 'mangos', 'plátanos' ]; console.log( lfEspanol.format( list ) ); // manzanas, mangos o plátanos 

→ Support

▍Intl.Locale ()

The concept of “regional standard” is usually much more than just the name of a language. This may include the type of calendar, information about the time cycles used, and the names of languages. The Intl.Locale(localeId, config) constructor Intl.Locale(localeId, config) used to create formatted Intl.Locale(localeId, config) strings based on the config object passed to it.

Intl.Locale object created using Intl.Locale contains all the specified regional settings. Its .toString() method produces a formatted regional standard string.

 const krLocale = new Intl.Locale( 'ko', {  script: 'Kore', region: 'KR',  hourCycle: 'h12', calendar: 'gregory' } ); console.log( krLocale.baseName ); // ko-Kore-KR console.log( krLocale.toString() ); // ko-Kore-KR-u-ca-gregory-hc-h12 

Here you can read about identifiers and locale tags in Unicode.

→ Support


Currently, JS has the static methods Promise.all() and Promise.race() . The Promise.all([...promises]) method returns a promise that is successfully resolved after all the promises passed to the method as an argument are resolved. This promise is rejected in the event that at least one of the promises transferred to it is rejected. The Promise.race([...promises]) method returns a promise, which is resolved after any of the promises transferred to it is resolved, and is rejected if at least one of such promises is rejected.

The community of JS developers was desperate for a static method, the promise returned that would be resolved after all the promises passed to it would be complete (allowed or rejected). In addition, we needed a method similar to race() , which would return a promise waiting for the resolution of any of the promises passed to it.

▍ Promise.allSettled () Method

The Promise.allSettled() method accepts an array of promises. The promise returned by him is permitted after all the promises are rejected or permitted. The result is that the promise returned by this method does not need a catch .

The fact is that this promise is always successfully resolved. The then block receives status and value from each promise in the order they appear.

 var p1 = () => new Promise(  (resolve, reject) => setTimeout( () => resolve( 'val1' ), 2000 ) ); var p2 = () => new Promise(  (resolve, reject) => setTimeout( () => resolve( 'val2' ), 2000 ) ); var p3 = () => new Promise(  (resolve, reject) => setTimeout( () => reject( 'err3' ), 2000 ) ); var p = Promise.allSettled( [p1(), p2(), p3()] ).then(  ( values ) => console.log( values ) ); //  [ {status: "fulfilled", value: "val1"}  {status: "fulfilled", value: "val2"}  {status: "rejected", value: "err3"} ] 

→ Support

▍ Method Promise.any ()

The Promise.any() method is similar to Promise.race() , but the promise returned by it does not execute the catch when one of the promises passed to this method is rejected.

Instead, he awaits the resolution of all promises. If no promises were allowed, then the catch block will be executed. If any of the promises is successfully resolved, then will be executed.


In this article, we looked at some of the JavaScript innovations discussed at the Google I / O 2019 conference. We hope you find something among them that is useful to you.

Dear readers! What do you especially miss in JavaScript?


All Articles