What's new in ECMAScript 2020 (ES2020)
Introduction
ES2020 (or ES11) is the version of ECMAScript that corresponds to this year (if you're reading it in 2020). Each year, a new version comes out, as the folks at ECMAScript accustomed us. Although it is new, it doesn't contain as many features as the one for 2015, ES6. But it has some cool ones, some that make the language more readable and understandable, and some that the community demanded a long time ago, as you will see in a bit.
The following features that will be presented are only a part of the yearly finished proposals, those that have reached Stage 4. If you want to check them all, check this link. Refer to this one for a complete explanation on how the general process of accepting a proposal works in ECMAScript. Enough talking, let's jump straight to the features.
Async Iterators
Even though many data sources are synchronous, some of them aren't. In that case, a simple iterator won't be enough. Inheriting functionality based on the iterator interface introduced in ES6 and having a very similar syntax to it, ES2020 introduces the interface called AsyncIterator.
const { value, done } = syncIterator.next(); // ES6
asyncIterator.next().then(({ value, done }) => /* code */); // ES2020
As for the synchronous iterator, the next() method returns a pair/tuple, where value is the next item in the sequence, and done is a boolean value stating if the end of the sequence has been reached. The only difference for the asynchronous iterator is that next() returns a promise for the pair/tuple.
On top of this, an enhanced for-of iteration statement is introduced in order to iterate over asynchronous iterable objects. A quick example of usage is shown in the code snippet below.
for await (const line of readLines(filePath)) {
console.log(line);
}
Obviously, as await is used in the for-await-of iteration statement, it can only be used in async functions. More on async iterators can be found on the official page of the proposal.
Optional Chaining
One of the most error-prone things in JavaScript is accessing nested objects and their properties. Each developer, inexperienced or not, has stumbled upon this problem at least a couple of times. In order to access a deep level property, we need to check at each level if the previous properties that get us to the desired property are defined. Otherwise, we would be getting back the classic 'undefined'. This can be perfectly explained with an example:
const object = {
property: {
property: {
property: "value"
}
}
}
if (object.property && object.property.property && object.property.property.property) {
object.property.property.property = "new value";
}
Luckily, the days of the code above are gone, as ES2020 introduces the optional chaining operator. We just need to use the '?.' to access nested objects. And if it reaches a property that’s null or undefined, then it just returns undefined. The code written above can be rewritten as follows:
const object = {
property: {
property: {
property: "value"
}
}
}
if (object?.property?.property?.property) {
object.property.property.property = "new value";
}
Official proposal page for the optional chaining operator.
Nullish Coalescing Operator
Another common problem which regards null or undefined is when trying to set a default value to a variable when we declare it. A common shorthand when initializing variables in JavaScript is using the '||' operator.
const x = y || z;
If y and z are not of type boolean, JavaScript implicitly converts them to booleans in order to evaluate the expression. If y gets evaluated to true, x is assigned the value of y, otherwise, x gets assigned the value of z. The problem here is that JavaScript has falsy values (false, 0, 0n, null, undefined, "", '', ``, and NaN) which evaluate to false when using the '||' operator. So, if we use falsy values in the place of y, x will be assigned the value of z, something that is not always wanted.
Fear not, as ES2020 introduces the nullish coalescing operator. Using '??' instead of '||', x will get assigned the value of z only if y is null or undefined.
const y = null;
const z = 2;
const x = y ?? z;
console.log(x); // 2
const yy = 1;
const zz = 2;
const xx = yy ?? zz;
console.log(xx) // 1
More on nullish coalescing operator can be found here.
BigInt
BigInt is a new primitive introduced with this version of ECMAScript. It serves the purpose of representing numbers greater than 253, which is the largest number that JavaScript can represent with the use of the Number primitive. Differentiating between a BigInt and a Number is done fairly easy, the letter n is appended at the end of the number.
const bigInt = 12345678987654321n;
const anotherBigInt = BigInt(12345678987654321);
console.log(anotherBigInt); // 12345678987654321n
All the operators (+, -, *, /, %, and **) work in the same way as they do when working with values of type Number. In what regards the equality criteria between BigInt and Number, it works as you would expect. Comparing them is straightforward as well.
1n == 1 // true
1n === 1 // false
1n < 2 // true
2n > 1 // true
2 < 2n // false
2n >= 2 // true
If you want to learn more about BigInt, follow its official proposal here.
Dynamic Imports
Another proposal that reached the final stage of the process is adding a variation of the import() function that permits dynamic imports at runtime. Prior to this, importing a module could only be done statically, by passing a string argument to the import() function, which would load it before runtime.
Regarding performance, by loading the imported modules at runtime, only when they are needed, the app boots up faster. Internationalization benefits from this feature as well. In most cases, the language is only known when running the app. Besides performance and internationalization, a lot of other factors may only be known at runtime, so dynamic imports come in handy.
Using this new import() function resembles the old one. We can pass a string to it as a parameter, only that it will not be interpreted as a string literal. Hence, something like this ./language-packs/${language}.js
will be permitted. The new import() function returns a promise for the specified module. An example of a dynamic import is shown below.
// code
const language = 'Spain';
import(`./language-packs/${language}.js`)
.then(module => {
// module loaded, display the page in Spanish
})
.catch(err => {
// treat the error
});
// more code
More about dynamic imports on the official proposal page.
Conclusion
JavaScript is evolving each year, by bringing new features and pleasing the community with some that have long been requested. ES2020 may not have such a huge and significant impact as ES6, but it provides some elegant solutions to tricks developers used to bypass certain language pitfalls.