ES2020: Everything You Need to Know
Tags: javascript, es2020, webdev
Published: 2020-05-05 11:10:14

---
title: ES2020: Everything You Need to Know
published: true
description: Everything you need to know about the latest ES2020 features and how they got into the language.
tags: javascript, es2020, webdev
---

# Another Year

Another year has passed, and we now know the latest features that have been finalised to go into ES2020, the latest specification of JavaScript/ECMAScript. The finalised proposals can be found [here](https://github.com/tc39/proposals/blob/master/finished-proposals.md) on the TC39 proposals GitHub repository. You may be wondering why or how these proposals are put together, and the journey of a JavaScript proposal before it goes into the language itself. Well, you are in luck.

# How Features are added to JavaScript

No, it's not [Brendan Eich](https://en.wikipedia.org/wiki/Brendan_Eich), or Google, or some supreme being. The JavaScript specification is managed and iterated by a committee called TC39 (Technical Committee 39). TC39 is made up of various developers, people from academia and platform enthusiasts.

TC39 meet around 6 times a year, mostly in the US but also in Europe. They work with the community to accept proposals for new JavaScript features and take them through the 4 "stages" of a JavaScript language proposal. The [4 stages](https://tc39.es/process-document/) are as follows:

* **Stage 0: Strawperson**

  You have made a proposal to the language. This is generally done by raising a PR against the \[TC39 ECMAScript GitHub repository](https://github.com/tc39/ecma262).
* **Stage 1: Proposal**

  A TC39 member has been identified as a "champion" who is on board with the idea of introducing your feature. A public GitHub repository is created around your proposal, with examples, high-level API, rationale and potential issues.
* **Stage 2: Draft**

  Things are starting to get real. The Draft stage now means that all of the syntax and semantics of your proposal need to be nailed down. This involves describing your proposal feature using the formal specification language you will see in the JavaScript spec itself.
* **Stage 3: Candidate**

  Stage 3 is when your proposal is pretty much ready to go - TC39 reviewers have signed off on the specification text. Responsibility falls to the community at this stage. Developers are expected to use the feature and provide feedback that is only possible through actually using it in their software. The only changes made to your proposal here will be critical bugs and problems that are uncovered by the community.
* **Stage 4: Finished**

  Done and dusted. Your proposal has been well tested in the community in real implementations. Your proposal will be included in the next version of the ECMAScript standard and will be used by millions.

In this blog post, we are going to dive into each of the confirmed, stage 4 features that are being added to ES2020, as well as some examples.

# String.prototype.matchAll

`String.prototype.matchAll` is a utility function that's used to get **all** the matches for a particular regular expression (including capturing groups, which will be explained later). How was this problem being solved before ES2020? Let's take a simple example and iterate. Say we have the following string:

```javascript
const test = "climbing, oranges, jumping, flying, carrot";
```

We want to get the **verb** from each of the words that end with `ing`. For example `climb` from "climbing" and `jump` from "jumping". This is a perfect job for regex. Lets walk through the steps.

* Search the string for any words that end in "ing" (eg. "climbing")
* Capture all the letters that come before "ing" in the word (eg. "climb")
* Return them

Okay, so to do that we might use the following regex.

```javascript
const regex = /([a-z]*)ing/g;
```

Regex is hard. Let's break it down so we understand how this works.

* `([a-z]*)` - match any string which contains the letters a-z in a row. We wrap this in parens `()` to make it a "capturing group". A capturing group is as the name suggests - we want to "capture" the group of characters that match **this particular part**. In our example, we want to match all words ending in "ing", but what we really want are the letters before that, hence using a capturing group.
* `ing` - only match strings that end in "ing"
* `/g` - global. Search the whole input string. Don't stop at the first match.

### `String.prototype.match`

Let's use our regex to find our verbs. One option in JavaScript is to use the `match` function on a string, which lets you pass in a regex expression.

```javascript
const test = "climbing, oranges, jumping, flying, carrot";
const regex = /([a-z]*)ing/g;

test.match(regex);

// ["climbing", "jumping", "flying"]
```

No good. It's returning the full words rather than just the verbs! This is happening because `match` does not support capturing groups with the `/g` flag - meaning that we can't use this to solve our problem. `match` would be fine if we didn't need to use capturing groups, but in this case we do. Let's try something else.

### `RegExp.prototype.exec`

The `exec` method is executed on a regex itself, rather than the string like `match`.  `exec` has support for capturing groups, but is a slightly more clunky API to use. You must keep calling `exec` over and over on the regex to get the next match. This requires us to create an infinite loop and continue calling `exec` until there are no matches left.

```javascript
const regex = /([a-z]*)ing/g;

const matches = [];

while (true) {
  const match = regex.exec(test);
  if (match === null) break;
  matches.push(match[1]);
}

matches
// ["climb", "jump", "fly"]
```

This approach works fine, but it is a little confusing and unintuitive. There are two primary reasons for this:

* It only performs the intended operation if the `/g` flag is set at the end. This can get a little confusing if you are passing the regex around as a variable or parameter.
* When using the `/g` flag, `RegExp` objects are stateful and store a reference to their last match. This can cause strange bugs if you are re-using the same regex over and over with multiple calls to `exec()`.

## Using String.prototype.matchAll

Finally - we have arrived. (If you skipped to this part, I don't judge you.) `String.prototype.matchAll` will make our lives much easier here and provide a simple solution that supports capturing groups, returning an iterable that we can spread into an array. Let's refactor our code above to use `matchAll`.

```javascript
const test = "climbing, oranges, jumping, flying, carrot";

const regex = /([a-z]*)ing/g;

const matches = [...test.matchAll(regex)];

const result = matches.map(match => match[1]);

result

// ["climb", "jump", "fly"]
```

We get a 2 dimensional array with the full word match in the first element ("climbing") as well as the captured group in the second element ("climb"). By iterating over and pulling out the second element, we get the results we want. Great!

# Dynamic import()

This is one you may be already familiar with. It's been supported by webpack since v2 and is common in production JavaScript applications for "code splitting". Code splitting is very powerful in a single page application - in a lot of cases speeding up initial page load times significantly. 

The dynamic import syntax allows us to call `import` as a function that returns a promise. This becomes useful for dynamically loading modules at runtime. For example, you may want to load a certain component or module based on some logic in your code.

```javascript
// JavaScript for side panel is loaded
  const sidePanel = await import("components/SidePanel");
  sidePanel.open();
```

Interpolation is also supported.

```javascript
async function openSidePanel(type = "desktop") {
	// JavaScript for desktop side panel is loaded
	const sidePanel = await import(`components/${type}/SidePanel`);
	sidePanel.open();
}
```

This feature enhances the performance of our applications. We don't have to load all the JavaScript up front. Dynamic imports give us the control to only load exactly as much JS as we need to.

# BigInt

![matrix](https://martinmck.com/images/matrix-numbers.jpg)

The largest number JavaScript can handle is `2^53`. That's `9007199254740991`, or you can use the slightly more memorable `Number.MAX_SAFE_INTEGER`.

What happens when you go beyond `MAX_SAFE_INTEGER`? Well, it's not so `SAFE` anymore. 

```javascript
console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991
console.log(Number.MAX_SAFE_INTEGER + 1); // 9007199254740992
console.log(Number.MAX_SAFE_INTEGER + 2); // 9007199254740992 - wut
console.log(Number.MAX_SAFE_INTEGER + 3); // 9007199254740994 - WUT
```

The `BigInt` type in ES2020 solves this. To convert a number literal to a `BigInt`, you can use the `BigInt` constructor, or you simply add a `n` to the end of it. So to fix our example above where we got the same value after adding 2 to `Number.MAX_SAFE_INTEGER`:

```javascript
BigInt(Number.MAX_SAFE_INTEGER) + 2n; // 9007199254740993n ✅
```

## Who needs these numbers?

You might be surprised to hear that it's quite common to have numbers this large in software development. Timestamps and unique identifiers can be numbers this large. 

For example, [Twitter](https://developer.twitter.com/en/docs/basics/twitter-ids) use integers this large as unique keys for tweets. You would see weird bugs in your JavaScript application if you tried to store these as numbers without `BigInt`. You would have to use a community package, or store them as a string instead - which is a common solution that JavaScript developers were using to solve this problem in environments where `BigInt` isn't supported. 

# Promise.allSettled

Let's say you are sitting an exam. When you receive your results, you find out you got 99% of the questions correct. In most walks of life, you would have passed with flying colours. In this case, though - you receive a big red stamp on your results letter telling you that you failed.

This is how [Promise.all](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all) works. `Promise.all` takes an array of promises, and concurrently fetches their results. If they **all** succeed, your `Promise.all` succeeds. If one or more fail, your promise rejects. In some cases you may want this behaviour - but not always.

## Enter Promise.allSettled

`Promise.allSettled` of ES2020 is much kinder when it comes to your exam. It will give you a pat on the back and tell you not to worry about that 1% of promises that failed. 

A promise is regarded as "settled" when it comes back - pass or fail. `Promise.allSettled` allows us to pass an array of promises and it will resolve when they are all settled. The return value of the promise is the array of results. Let's look at an example.

```javascript
const promises = [
  fetch('/api1'),
  fetch('/api2'),
  fetch('/api3'),
];

Promise.allSettled(promises).
  then((results) => results.forEach((result) => console.log(result.status)));

// "fulfilled"
// "fulfilled"
// "rejected"
```

# globalThis

We live in a world where "universal" JavaScript is common. This means that the same JavaScript code could be running on the client and on the server in NodeJS. This presents a particular set of challenges. 

One is the global object, accessible from any piece of running code. This is `window` in the browser, but `global` in Node. Writing universal code that accesses this global object relies on some conditional logic, that may look something like this (cover your eyes).

```javascript
(typeof window !== "undefined"
? window
: (typeof process === 'object' &&
   typeof require === 'function' &&
   typeof global === 'object')
	? global
	: this);
```

Thankfully, ES2020 brings with it the addition of the `globalThis` global variable. This will do the heavy lifting above for you and means you can now relax when it comes to accessing `window` or `global` in either front-end or back-end code.

```javascript
globalThis.something = "Hello"; // Works in Browser and Node.
```

# for-in mechanics

`for (x in obj) ...` is a super useful syntax for many things - mainly iterating over the keys of an object.

```javascript
for (let key in obj) {
  console.log(key);                      
}
```

This proposal is related to the **order and semantics** of which elements are iterated in a `for..in` loop. Before this proposal, most JavaScript engines had already applied common sense - currently all major browsers loop over the properties of an object in the order in which they were defined. There were some nuances, however. These mainly involved more advanced features like proxies. `for..in` loop semantics have historically been left out the JavaScript spec, but this proposal ensures everyone has a consistent point of reference as to how `for..in` should work.

# Optional Chaining

![chain](https://martinmck.com/images/matthew-lancaster-mthzsf_blzs-unsplash.jpg)

Optional chaining is probably one of the most highly anticipated features to come to JavaScript in quite some time. In terms of impact on cleaner JavaScript code, this one scores very highly.

When checking for a property deep inside a nested object, you often have to check for the existence of intermediate objects. Let's work through an example.

```javascript
name: "foo",
  age: 25,
  address: {
    number: 44,
    street: "Sesame Street",
    city: {
      name: "Fake City",
      lat: 40,
      lon: 74
    }
  }
}

// when we want to check for the name of the city
if (test.address.city.name) {
  console.log("City name exists!");
}

// City Name exists!
```

This works fine! But in software, we can't always rely on the happy path. Sometimes intermediate values will not exist. Let's look at the same example, but with no `city` value defined.

```javascript
const test = {
  name: "foo",
  age: 25,
  address: {
    number: 44,
    street: "Sesame Street"
  }
}

if (test.address.city.name) {
  console.log("City name exists!");
}

// TypeError: Cannot read property 'name' of undefined
```

Our code is broken. This happens because we are trying to access `name` on `test.address.city` which is `undefined`. When you attempt to read a property on `undefined`, the above `TypeError` will be thrown. How do we fix this? In a lot of JavaScript code, you will see the following solution.

```javascript
const test = {
  name: "foo",
  age: 25,
  address: {
    number: 44,
    street: "Sesame Street"
  },
  
}

if (test.address && test.address.city && test.address.city.name) {
  console.log("City name exists!");
}

// no TypeError thrown!
```

Our code now runs, but we've had to write quite a bit of code there to solve the problem. We can do better. The Optional Chaining operator of ES2020 allows you to check if a value exists deep inside an object using the new `?` syntax. Here is the above example rewritten using the optional chaining operator.

```javascript
const test = {
  name: "foo",
  age: 25,
  address: {
    number: 44,
    street: "Sesame Street"
  },
  
}

// much cleaner.
if (test?.address?.city?.name) {
  console.log("City name exists!");
}

// no TypeError thrown!
```

Looking good. We have condensed the long `&&` chains into our much more succinct and readable optional chaining operator. If any of the values along the chain are `null` or `undefined`, the expression simply returns `undefined`. 

The optional chaining operator is very powerful. Have a look at the following examples to see other ways it can be used.

```javascript
const nestedProp = obj?.['prop' + 'Name']; // computed properties

const result = obj.customMethod?.(); // functions

const arrayItem = arr?.[42]; // arrays
```

# Nullish Coalescing Operator (`null` or `undefined`)

The Nullish Coalescing Operator is a very fancy sounding name for something very simple. What this feature gives us is the ability to check if a value is `null` or `undefined` and default to another value if so - nothing more, nothing less.

Why is this useful? Let's take a step back. There are **five** "falsy" values in JavaScript.

* `null`
* `undefined`
* empty string ("")
* `0`
* Not a number -`NaN`

We may have some code where we want to check against a numeric value. We want to assign a squad number to players in a team. If they already have a squad number, we keep that. Otherwise, we give them the value of "unassigned". 

```javascript
const person = {
  name: "John",
  age: 20,
  squadNumber: 100
};

const squadNumber = person.squadNumber || "unassigned";

console.log(`${person.name}s squad number is ${squadNumber}`);


// "Johns squad number is 100"
```

This code works fine. However let's think about this from a slightly different angle. What if our `person` had a slightly obscure squad number, like zero? 

```javascript
const person = {
  name: "Dave",
  age: 30,
  squadNumber: 0
};

const squadNumber = person.squadNumber || "unassigned";

console.log(`${person.name}s squad number is ${squadNumber}`);


// "Daves squad number is unassigned"
```

This isn't right. Dave has been playing for the team for years. Our code has a bug. This happens because `0` is falsy, causing the false condition of our `||` to be invoked. This example is where the standard check for a value can fall short. You can of course, solve this by doing the following:

```javascript
const person = {
  name: "Dave",
  age: 30,
  squadNumber: 0
};

const squadNumber = person.squadNumber >= 0 ? person.squadNumber : "unassigned";

console.log(`${person.name}s squad number is ${squadNumber}`);


// "Daves squad number is 0"
```

Not a bad solution - but we can do better using the Nullish Coalescing operator (`??`) to **ensure** that our value is `null` or `undefined`, before we say someones squad number is `unassigned`.

```javascript
const person = {
  name: "Dave",
  age: 30,
  squadNumber: 0
};

// Nullish Coalescing Operator
// If person.squadNumber is null or undefined
// set squadNumber to unassigned
const squadNumber = person.squadNumber ?? "unassigned";

console.log(`${person.name}s squad number is ${squadNumber}`);


// "Daves squad number is 0"
```

Nothing wrong with that little bit more type safety and explicitness in our JavaScript.

# import.meta

`import.meta` is a convenience property that provides an object containing the base URL of the currently running **module**. If you are familiar with node, this functionality is available out of the box with CommonJS through the `__dirname` or `__filename` properties.

```javascript
const fs = require("fs");
const path = require("path");
// resolves data.bin relative to the directory of this module
const bytes = fs.readFileSync(path.resolve(__dirname, "data.bin"));
```

What about the browser though? This is where `import.meta` becomes useful. If you want to import a relative path from a JavaScript module running in the browser, you can use `import.meta` to do so.

```javascript
// Will import cool-image relative to where this module is running.
const response = await fetch(new URL("../cool-image.jpg", import.meta.url));
```

This feature is very useful for library authors - as they do not know how and where you will run your code.

# Conclusion

All in all, the newest features added to the ECMAScript specification add even more utility, flexibility and power to the constantly evolving and developing ecosystem that is JavaScript. It's encouraging and exciting to see the community continue to thrive and improve at such a rapid pace.

You may be thinking - "That all sounds great.. but how do I get started using ES2020 features?"

## When/how can I use this stuff?

You can use it now! In the later versions of most modern browsers and Node, you will have support for all of these features. caniuse.com is another great resource to check levels of compatibility for ES2020 features across browsers and node. 

If you need to use these features on older browsers or versions of node, you will need babel/typescript.

## Enabling ES2020 support

### Using Babel 7.8.0 or TypeScript 3.7

Babel 7.8.0 and above as well as TypeScript 3.7 and above support ES2020 features out of the box.

### Using babel plugins

If you can't upgrade your babel setup, you will have to install the ES2020 features through a multitude of plugins. Here's an example .babelrc config of how you may do that.

```json
{
  "plugins": [
    "@babel/plugin-proposal-nullish-coalescing-operator",
    "@babel/plugin-proposal-optional-chaining",
    "@babel/plugin-proposal-class-properties",
    "@babel/plugin-syntax-bigint"
  ]
}
```

Feel free to reach out to me or follow me on [Twitter](https://twitter.com/shogun_purple), where I tweet and blog about JavaScript, Python, AWS, automation and no-code development. 

I hope you learned something. Thanks for reading!