Skip to content

ECMAScript 2020 (ES11)

Optional Chaining

I'm sure that most developers are familiar with an error of this kind:

TypeError: Cannot read property 'x' of undefined

This error basically means that we tried to access a property on something that is not an object.

Accessing an Object Property
const flower = {
    colors: { red: true }
}
console.log(flower.colors.red) // this will work
console.log(flower.species.lily) // TypeError: Cannot read property 'lily' of undefined

The optional chaining operator, which consists of a question mark and a dot: ?., can be used to indicate that an error should not be thrown. Instead, if there is no value, undefined will be returned.

console.log(flower.species?.lily) // undefined

Optional chaining can also be used when accessing array values or calling a function.

let flowers =  ['lily', 'daisy', 'rose']
console.log(flowers[1]) // daisy
flowers = null
console.log(flowers[1]) // TypeError: Cannot read property '1' of null
console.log(flowers?.[1]) // undefined

let plantFlowers = () => {
  return 'orchids'
}
console.log(plantFlowers()) // orchids
plantFlowers = null
console.log(plantFlowers()) // TypeError: plantFlowers is not a function
console.log(plantFlowers?.()) // undefined

Nullish Coalescing

Until recently, whenever there was a need to provide a fallback value, the logical operator || had to be used. It works in most cases, but it can't be applied in some scenarios. For instance, if the initial value is a Boolean or a number. Let's take a look at an example below, where we want to assign a number to a variable, or default it to 7 if the initial value is not a number:

let number = 1
let myNumber = number || 7

The myNumber variable is equal to 1, because the left-hand value (number) is a truthy value, as 1 is a positive number. However, what if the number variable is not 1, but 0?

let number = 0
let myNumber = number || 7

0 is a falsy value, and even though it is a number, the myNumber variable will have the right-hand value assigned to it. Therefore, myNumber is now equal to 7. However, that's not really what we want. Fortunately, instead of writing additional code and checks to confirm if the number variable is indeed a number, we can use the nullish coalescing operator. It consists of two question marks: ??.

let number = 0
let myNumber = number ?? 7

The right-hand side value will only be assigned if the left-hand value is equal to null or undefined. Therefore, in the example above, the myNumber variable is equal to 0.

Private Fields

Many programming languages that have classes allow defining class properties as public, protected, or private. Public properties can be accessed from outside of a class and by its subclasses, while protected classes can only be accessed by subclasses. However, private properties can only be accessed from inside of a class. JavaScript supports class syntax since ES6, but only now were private fields introduced. To define a private property, it has to be prefixed with the hash symbol: #.

class Flower {
  #leaf_color = "green";
  constructor(name) {
    this.name = name;
  }

  get_color() {
    return this.#leaf_color;
  }
}

const orchid = new Flower("orchid");

console.log(orchid.get_color()); // green
console.log(orchid.#leaf_color) // Private name #leaf_color is not defined

If we try to access a private property from outside, an error will be thrown.

Static Fields

To use a class method, a class had to be instantiated first, as shown below.

class Flower {
  add_leaves() {
    console.log("Adding leaves");
  }
}

const rose = new Flower();
rose.add_leaves();

Flower.add_leaves() // TypeError: Flower.add_leaves is not a function

Trying to access a method without instantiating the Flower class would result in an error. Thanks to static fields, a class method can now be declared with the static keyword and called from outside of a class.

class Flower {
  constructor(type) {
    this.type = type;
  }
  static create_flower(type) {
    return new Flower(type);
  }
}

const rose = Flower.create_flower("rose"); // Works fine

Top Level Await

So far, to await for a promise to finish, a function in which await is used would need to be defined with the async keyword.

const func = async () => {
    const response = await fetch(url)
}

Unfortunately, if there was a need to await for something in a global scope, it would not be possible, and usually required an immediately invoked function expression (IIFE).

(async () => {
    const response = await fetch(url)
})()

Thanks to Top Level Await, there is no need for wrapping code in an async function anymore, and this code will work.

const response = await fetch(url)

This feature could be useful for resolving module dependencies or using a fallback source if the initial one failed.

let Vue
try {
    Vue = await import('url_1_to_vue')
} catch {
    Vue = await import('url_2_to_vue)
}

Promise.allSettled

To wait for multiple promises to finish, Promise.all([promise_1, promise_2]) can be used. The problem is that if one of them fails, then an error will be thrown. Nevertheless, there are cases in which it is ok for one of the promises to fail, and the rest should still resolve. To achieve that, ES11 introduced Promise.allSettled.

promise_1 = Promise.resolve('hello')
primise_2 = new Promise((resolve, reject) => setTimeout(reject, 200, 'problem'))

Promise.allSettled([promise_1, promise_2])
    .then(([promise_1_result, promise_2_result]) => {
        console.log(promise_1_result) // {status: 'fulfilled', value: 'hello'}
        console.log(promise_2_result) // {status: 'rejected', reason: 'problem'}
    })

A resolved promise will return an object with status and value properties, while rejected ones will have status and reason.

Dynamic Import

You might have used dynamic imports when using webpack for module bundling. Finally, native support for this feature is here.

// Alert.js file
export default {
    show() {
        // Your alert
    }
}


// Some other file
import('/components/Alert.js')
    .then(Alert => {
        Alert.show()
    })

Considering the fact that a lot applications use module bundlers like webpack for transpiling and optimizing code, this feature isn't such a big deal right now.

MatchAll

MatchAll is useful for applying the same regular expression to a string if you need to find all matches and get their positions. The match method only returns items that were matched.

const regex = /\b(apple)+\b/;
const fruits = "pear, apple, banana, apple, orange, apple";

for (const match of fruits.match(regex)) {
  console.log(match);
}
// Output
//
// 'apple'
// 'apple'

matchAll in contrast, returns a bit more information, including index of the string found.

for (const match of fruits.matchAll(regex)) {
  console.log(match);
}

// Output
//
// [
//   'apple',
//   'apple',
//   index: 6,
//   input: 'pear, apple, banana, apple, orange, apple',
//   groups: undefined
// ],
// [
//   'apple',
//   'apple',
//   index: 21,
//   input: 'pear, apple, banana, apple, orange, apple',
//   groups: undefined
// ],
// [
//   'apple',
//   'apple',
//   index: 36,
//   input: 'pear, apple, banana, apple, orange, apple',
//   groups: undefined
// ]

globalThis

JavaScript can run in different environments like browsers or Node.js. A global object in browsers is available under window variable, but in Node it is an object called global. To make it easier to use a global object no matter in which environment code is running, globalThis was introduced.

// In a browser
window == globalThis // true

// In node.js
global == globalThis // true

BigInt

The maximum number that can be reliably represented in JavaScript is 2^53 - 1. BigInt will allow creation of numbers even bigger than that.

const theBiggerNumber = 9007199254740991n
const evenBiggerNumber = BigInt(9007199254740991)