What's new in ECMAScript 2020
Tags: javascript
Published: 2020-09-05 19:35:45

ECMAScript 2020, the 11th installment of our favorite programming language, contains a handful of new features. Some are small ones, but others have the potential to change forever the way we write JavaScript.

This article is an attempt at a short and concise overview of those new features. Grab your cup of tea and let's go.

## Dynamic import()

ES2015 introduced the `static import` syntax. Now you could export a variable from one module and then import it in another.

```javascript
// utils.js
export function splitName(name) {
  return name.split(" ");
}

// index.js
import { splitName } from "./utils";

console.log(splitName("John Snow"));
```

That syntax is called static because you can't import a module dynamically (depending on some conditions) at runtime. Note that it's not necessarily a bad thing: static imports can be optimized at compile time, allowing for [Tree Shaking](https://webpack.js.org/guides/tree-shaking/).

Dynamic imports, on the other hand, when used wisely, can help with reducing bundle size by loading dependencies on-demand.

The new `dynamic import` syntax looks like a function (but it's not), and it returns [a promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise), which also means we can use [async/await](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) with it.

```javascript
// ...
const mod = figure.kind === "rectangle" ? "rectangle.js" : "circle.js";
const { calcSquare } = await import(mod);
console.log(calcSquare(figure));
```

## Nullish coalescing

The popular way of setting a default value with [short-circuting](https://en.wikipedia.org/wiki/Short-circuit_evaluation) has its flaws. Since it's not really checking for the emptiness, but rather checking for the [falsyness](https://developer.mozilla.org/en-US/docs/Glossary/Falsy), it breaks with values like `false`, or `0` (both of which are considered to be falsy).

ES2020 introduces a new operator `??` which works similarly but only evaluates to the right-hand when the initial value is either `null` or `undefined`.

Here's a quick example:

```javascript
const initialVal = 0;

// old way
const myVar = initialVal || 10; // => 10

// new way
const myVar = initialVal ?? 10; // => 0
```

[I wrote a detailed article](https://www.strictmode.io/articles/nullish-coalescing-vs/) about this feature and how it compares to the other methods for setting a default value.

## Optional chaining

The new `optional chaining` operator aims to make the code shorter when dealing with nested objects and checking for possible `undefineds`.

```javascript
const user = { name: "John" };

// Fails with `Uncaught TypeError: Cannot read property 'city' of undefined`
const city = user.address.city;

// Works but verbose
let city = "Not Set";
if (user.address !== undefined && user.address !== null) {
  city = user.address.city;
}

// Works and concise but requires a 3rd party library
const city = _.get(user, "address.city", "Not Set");

// 🤗
const city = user?.address?.city ?? "Not Set";
```

## BigInt

`BigInt` is a new object that represents numbers higher than `Number.MAX_SAFE_INTEGER` (which is `2^53 - 1`). While for normal folks, it may sound more than enough, for some math applications and machine learning, the new `BigInt` type comes in handy.

It comes with its own literal notation (just add an `n` to a number):

```javascript
const x = 9007199254740991n;

// or it can be constructed from a string
const y = BigInt("9007199254740991234");
```

`BigInts` come with their own algebra, which is not translated to regular numbers by which we can't mix up numbers and BigInts. They should be coerced to either type first.

```javascript
1 === 1n; // => false
1n + 1; // throws Uncaught TypeError: Cannot mix BigInt and other types, use explicit conversions
6n << 3; // nope
6n << 3n; // that works
```

## String.matchAll

So here's an example. Imagine, you have a very long string of text, and you need to extract all the tags (that are words starting with `#`) out of it. [Regular expressions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions) to the rescue!

```javascript
const tweet = "#JavaScript is full of #surprises. Both good and bad ones #TIL";

for (h of tweet.matchAll(/(#\w+)/g)) {
  console.log(h[0]);
}

// or

const tags = [...tweet.matchAll(/(#\w+)/g)]
```

`matchAll` returns an iterator. We could either iterate over it with `for..of`, or we can convert it to an array.

## Promise.allSettled

Remember the [Promise.all](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all) function? It resolves only when all of the passed promises get resolved. It rejects if at least one of the promises got rejected, while the others may still be pending.

The new `allSettled` behaves differently. It resolves whenever all of the promises finished working, that is, became either fulfilled or rejected. It resolves to an array that contains both the status of the promise and what it resolved to (or an error).

Thus, `allSettled` is never rejected. It's either `pending`, or `resolved`.

A real-world problem might be removing a loading indicator:

```
// const urls = [...]
try {
  await Promise.all(urls.map(fetch))
} catch (e) {
  // at least one fetch is rejected here, but there may others still pending
  // so it may be too early for removing the loading indicator
  removeLoading()
}

// with allSettled
await Promise.allSettled(urls.map(fetch))
removeLoading()
```

## globalThis

In JavaScript, there's always one big context object that contains everything. Traditionally, in browsers it was `window`. But if you try accessing it in Node application, you'll get an error. There is no `window` global object in Node; instead there is `global` object. Then again, in WebWorkers, there is no access to `window`, but there is `self` instead.

The new `globalThis` property abstracts away the difference. Meaning that you can always refer to `globalThis` without caring in which context you are now.

Now, if you think that the naming is rather awkward, I'm totally with you, but note that naming it `self` or `global` could make some older code incompatible. So I guess we'll have to live with that.

## What's next?

For your convenience, here are the links to the MDN documentation for each of the features mentioned in this article.

- [Dynamic imports](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import)
- [Nullish coalescing operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_operator) but also [my own article](https://www.strictmode.io/articles/nullish-coalescing-vs/)
- [Optional chaining](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining)
- [BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt)
- [Promise.allSettled](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled)
- [globalThis](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis)

If you like articles like this, you can [follow me on Twitter](https://twitter.com/janis_t) to get notified about the new ones.