JavaScript: Guide to New ECMAScript 2020 Features
By: Mosh Hamedani
Tags: JavaScript, Javascript Development, Ecmascript 2020, Ecmascript, Javascript Tips

ECMAScript is the specification upon which JavaScript is based. And since 2015, it has been evolving year after year, with the addition of features like classes, modules, async/await and many others. And what makes things even better is that today’s browsers add support for new features quickly, with a short time between releases. In this article, you’ll find out what is new in ECMAScript 2020. Have a good read!

GlobalThis

In modern applications, it’s normal to have the same JavaScript code running in multiple environments. It’s also very common to need to access the global object, which, depending on the context, is represented by a different variable. In browsers, you need to use window. Node.js calls it global. In a web worker, it is self. ECMAScript 2020 makes it easier to access this object by providing a globalThis variable:

// In the browser

console.log(globalThis === window); // => true // In Node.js

console.log(globalThis === global); // => true // In a web worker

console.log(globalThis === self); // => true

Nullish coalescing operator

The OR operator is widely used to set the default value of an expression in JavaScript. The first operand is the tested value and the second one is the value to be used in case the first one is falsy:

const config = options || {};

The problem with the above technique is that null and undefined are not the only falsy values that exist in JavaScript. Even values like an empty string (“”) or zero (0) are evaluated to false (and let’s not forget false itself). And sometimes, these are acceptable values and should not be defaulted to other values, like in the following example:

const delay = windowAnimDelay || 300;

const title = windowTitle || 'Alert';

In the above snippet, what if we wanted to have a delay of 0 milliseconds or to set the window title to “” (empty string)? We simply couldn’t. At least not without changing our logic. That’s why ECMAScript 2020 provides the new nullish coalescing operator (represented by two question marks “??”), whose usage is identical to the OR operator, but it only defaults to the second operand in case the first one is null or undefined. In all other cases, the expression returns the first operand as its value:

const delay = windowAnimDelay ?? 300; // delay is only set to 300 in case windowAnimDelay is null or undefined

const title = windowTitle ?? 'Alert'; // title is only set to 'Alert' in case windowTitle is null or undefined

Optional chaining operator

When navigating through an object’s keys, we need to be careful to not try to access a property from null or undefined. Otherwise, this could result in a “Cannot read property … of undefined / null” error. Another thing to be cautious of is trying to execute a variable or object’s property as a function when it may be null or undefined. That would throw a “… is not a function” error. Here’s an example to make things clearer:

const someObj = {};

someObj.nonExistentProp1.nonExistentProp2.nonExistentProp3; // Error >> "Cannot read property 'nonExistentProp2' of undefined" const nonFunction = null;

nonFunction(); // Error >> "nonFunction is not a function" const anotherObj = {};

anotherObj.nonExistentFunction(); // Error >> "anotherObj.nonExistentFunction is not a function"

The above issues could’ve been prevented by using the AND operator to test each property, but when you’re navigating through multiple nested objects that could lead to a very long expression. Thankfully, ECMAScript 2020 has an elegant solution for this need, the optional chaining operator:

const someObj = {};

someObj?.nonExistentProp1?.nonExistentProp2?.nonExistentProp3; // undefined const nonFunction = null;

nonFunction?.(); // undefined const anotherObj = {};

anotherObj.nonExistentFunction?.(); // undefined const nonArray = null;

nonArray?.[10]; // undefined

All of the above expressions are evaluated to undefined and no errors are thrown anymore.

Dynamic import

ECMAScript 2015 modules made code a lot easier to read and organize. But since this feature was released, JS developers have been asking for a way to conditionally import modules. This was not possible with conventional ES imports, since they are evaluated at load time. Another limitation is that you couldn’t use a variable or string interpolation in the module path string. Now, all of this is possible with dynamic imports. They use the import keyword as a function call and return a promise, which is resolved when the imported module is fully loaded. You can even combine them with async/await:

// ...

const utilsPath = '../utils'; if (keys) {

const keysUtilsModule = await import(`${utilsPath}/keys.js`);

keys = keysUtilsModule.getKeys();

}

Import.meta

The import.meta object has a single property (for now), which is url. This property contains the URL of the current module. As expected, import.meta can only be accessed from within a module, otherwise, you’ll get a “Cannot use ‘import.meta’ outside a module” error.

// from within an utils.js module

console.log(import.meta.url); // => file:///Users/johndoe/Documents/sampleApp/utils.js

New export syntax

Now, it’s possible to assign a name to an export * from. The syntax is the same as named imports: export * as SomeName from ‘./someName’ . This is useful when you’re re-exporting multiple modules from a single file and don’t want them to conflict with each other. By naming each export * statement, you’re grouping them into separate objects, which can be imported and accessed individually, without conflicts:

// utils/general.js export const double = n => n * 2;

export const triple = n => n * 3;

export const sayHello = name => console.log(`Hello, ${name}!`); // utils/common.js export const sayHello = name => console.log('Hello!!!'); // utils/index.js export * as General from './general.js';

export * as Common from './common.js'; // index.js

import { General, Common } from './utils'; General.sayHello('Joe'); // => "Hello, Joe!"

Common.sayHello(); // => "Hello!!!"

BigInt

ECMAScript 2020 introduced a new data type for when you need to represent really big numbers: it’s BigInt. It can hold substantially bigger numbers than the standard Number type (whose limit is 2ˆ53, represented by the Number.MAX_SAFE_INTEGER constant). In fact, ECMAScript doesn’t specify a limit on how big a BigInt number can be. This limit depends on each implementation. You can declare a BigInt number by appending a n to it, like this:

9827923792782973982136782n

1627812765832326536253237625687121265172162751362763526536238276516728n

1782687161771821657313n * 62752362532256357621521726116524n

You can also convert a string to BigInt by using the BigInt constructor:

const aBigNumber = BigInt('716821762188913791217265713657761267152189271892712189127918');

Note that you can’t mix BigInts and numbers:

const result = 1782671n + 30; // => throws "Cannot mix BigInt and other types, use explicit conversions"

To make the above snippet work, you’d need to convert the second value (30) explicitly, by using the BigInt constructor.

const result = 1782671n + BigInt(30);

Promise.allSettled

Promise.allSettled takes an array of promises A as argument, runs them and returns a promise B that is resolved when all promises in A are settled, regardless of whether they are resolved or rejected. The result of B will be an array containing the results of all promises in A. Promise.allSettled is very similar to Promise.all, but the latter fails if some of the supplied promises is rejected, while the former resolves no matter what.

const roles = await Promise.allSettled([getSharedRoles(), getCustomRoles()]);

const failed = roles

.filter(r => r.status === 'rejected')

.map(r => r.reason);

String.matchAll

String.matchAll is a succinct and optimized way to get all results matching a string against a RegExp. Check out this example:

const regex = /name: ([a-zA-Z]+)/g;

const someText = 'name: John foo bar name: Mike baz bar name: Jane foo'; [...someText.matchAll(regex)].forEach(m => {

console.log(m);

})

Which outputs:

["name: John", "John", index: 0, input: "name: John foo bar name: Mike baz bar name: Jane foo", groups: undefined]

["name: Mike", "Mike", index: 19, input: "name: John foo bar name: Mike baz bar name: Jane foo", groups: undefined]

["name: Jane", "Jane", index: 38, input: "name: John foo bar name: Mike baz bar name: Jane foo", groups: undefined]

For-in order

ECMAScript 2020 included a small update to explicitly define the order that for-in loops should follow. Before this release, ECMAScript didn’t specify it. In practice, this shouldn’t affect your life, since browsers already handled it very consistently for quite some time. But in case you want to know more about this change, here’s the original proposal.

Conclusion

globalThis represents the global object on any environment;

The nullish coalescing operator (??) allows you to set default values in case the first operand is null or undefined;

The optional chaining operator (?) makes it easier and safer to navigate through nested objects;

Dynamic imports provide a way to conditionally import modules and use variables and interpolations to compose their paths;

The import.meta object provides meta information about the current module. For now, the only available property is url;

Now, it’s possible to define a name when on export * from statements;

BigInt is a new data type which can hold numbers significantly bigger than the Number type;

Promise.allSettled is similar to Promise.all, but will resolve even when some of the promises provided as argument is rejected;

String.matchAll allows you to get all results matching a string against a regular expression;

For-in loops now have a standardized order.

Would you like to improve your programming skills and become a JavaScript expert? If so, I recommend you check out Mosh’s JavaScript course. And if you liked this article, share it with others as well!