ES2021 / ES12 New Features

ECMAScript 2021 version is expected to be released in the month of June 2021. Here are some of the features we can expect in ES2021 or ES12. The list prepared based on ECMAScript proposal status and the new features released by Google Chrome V8 engine.

All the features listed below, at the time of writing is supported in Google Chrome Canary build.

String replaceAll() Method

String.prototype.replaceAll() replaces all occurrence of a string with another string value.

Currently JavaScript string has a replace() method. It can be used to replace a string with another string.

const str = "Backbencher sits at the Back";
const newStr = str.replace("Back", "Front");
console.log(newStr); // "Frontbencher sits at the Back"

If the input pattern is a string, replace() method only replaces the first occurrence. That is why in the code, the second occurrence of "Back" is not replaced.

We can do a full replacement only if we supply the pattern as a regular expression.

const str = "Backbencher sits at the Back";
const newStr = str.replace(/Back/g, "Front");console.log(newStr); // "Frontbencher sits at the Front"

String.prototype.replaceAll() is trying to bring the full replacement option even when the input pattern is a string.

Private Methods

Private methods can be accessible only inside the class where it is defined. Private method names starts with #.

class Person {

  // Private method
  #setType() {
    console.log("I am Private");
  }

  // Public method
  show() {
    this.#setType();
  }

}

const personObj = new Person();
personObj.show(); // "I am Private";
personObj.setType(); // TypeError: personObj.setType is not a function

Since setType() is a private method, personObj.setType returns undefined. Trying to use undefined as a function throws TypeError.

Private Accessors

Accessor functions can be made private by prepending # to the function name.

class Person {
  // Public accessor
  get name() { return "Backbencher" }
  set name(value) {}

  // Private accessor
  get #age() { return 42 }
  set #age(value) {}
}

In the above code get and set keywords make name an accessor property. Even though name looks like a function, it can be read like a normal property.

const obj = new Person();
console.log(obj.name); // "Backbencher"
console.log(obj.age); // undefined

WeakRef and Finalizers

WeakRef stands for Weak References. Main use of weak references is to implement caches or mappings to large objects. In such scenarios, we do not want to keep a lot of memory for a long time saving this rarely used cache or mappings. We can allow the memory to be garbage collected soon and later if we need it again, we can generate a fresh cache.

JavaScript is a garbage collected language. If a variable is no longer reachable, JavaScript garbage collector automatically removes it. You can read more on JavaScript garbage collection here in MDN site.

Consider the following code:

const callback = () => {
  const aBigObj = {
    name: "Backbencher"
  };
  console.log(aBigObj);
}

(async function(){
  await new Promise((resolve) => {
    setTimeout(() => {
      callback();
      resolve();
    }, 2000);
  });
})();

The code might look complicated. But basically, what we do is create a function named callback() and execute it using setTimeout(). The async wrapping is just to use await functionality. await is a feature in ES6, that helps to execute asynchronous code in synchronous way.

When executing above code, it prints "Backbencher" after 2 seconds. Based on how we use the callback() function, aBigObj is stored in memory forever, may be.

Let us make aBigObj a weak reference.

const callback = () => {
  const aBigObj = new WeakRef({    name: "Backbencher"  });  console.log(aBigObj.deref().name);}

(async function(){
  await new Promise((resolve) => {
    setTimeout(() => {
      callback(); // Guaranteed to print "Backbencher"
      resolve();
    }, 2000);
  });

  await new Promise((resolve) => {
    setTimeout(() => {
      callback(); // No Gaurantee that "Backbencher" is printed
      resolve();
    }, 5000);
  });
})();

A WeakRef is created using new WeakRef(). Later the reference is read using .deref() method. Inside the async function, The first setTimeout() will surely print the value of name. That is guaranteed in the first turn of event loop after creating the weak reference.

But there is no guarantee that the second setTimeout() prints "Backbencher". It might have been sweeped by the gargage collector. Since the garbage collection works differently in different browsers, we cannot guarantee the output. That is also why, we use WeakRef in situations like managing cache.

Finalizers

FinalizationRegistry is a companion feature of WeakRef. It lets programmers register callbacks to be invoked after an object is garbage collected.

const registry = new FinalizationRegistry((value) => {
  console.log(value);
});

Here registry is an instance of FinalizationRegistry. The callback function passed to FinalizationRegistry gets triggered when an object is garbage collected.

(function () {
  const obj = {};
  registry.register(obj, "Backbencher");
})();

Line 3 attaches obj to registry. When obj is garbage collected, the second argument of .register() method is passed to the callback function. So, according to our code logic, when obj is garbage collected, "Backbencher" is passed to the callback function and is printed in the console.

When I executed above code in Google Chrome Canary console, after about 1 min, it printed "Backbencher" in the console. Another way to force garbage collection in chrome is to click on Collect Garbage icon. We can find it in Performance tab.

Chrome Garbage Collection

Promise.any() and AggregateError

Promise.any() resolves if any of the supplied promises is resolved. Below we have 3 promises, which resolves at random times.

const p1 = new Promise((resolve, reject) => {
  setTimeout(() => resolve("A"), Math.floor(Math.random() * 1000));
});
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => resolve("B"), Math.floor(Math.random() * 1000));
});
const p3 = new Promise((resolve, reject) => {
  setTimeout(() => resolve("C"), Math.floor(Math.random() * 1000));
});

Out of p1, p2 and p3, whichever resolves first is taken by Promise.any().

(async function() {
  const result = await Promise.any([p1, p2, p3]);
  console.log(result); // Prints "A", "B" or "C"
})();

What if none of the promises resolve? In that case Promise.any() throws an AggregateError exception. We need to catch it and handle it.

const p = new Promise((resolve, reject) => reject());

try {
  (async function() {
    const result = await Promise.any([p]);
    console.log(result);
  })();
} catch(error) {
  console.log(error.errors);
}

For demo purpose, only one promise is passed to Promise.any(). And that promise is rejected. The above code logs following error in console.

AggregateError

Logical Assignment Operator

Logical assignment operator combines the logical operations(&&, || or ??) with assignment.

var x = 1;
var y = 2;
x &&= y;console.log(x); // 2

Line 3 operation can be expanded to:

x && (x = y)

Or in other way, it is like:

if(x) {
  x = y
}

Since x is a truthy value, it is assigned with the value of y, ie 2.

Just like the way we did with &&, we can do with || and ??.

x &&= y;
x ||= y;
x ??= y;

Logical assignment operator with ||

Here is the code.

var x = 1;
var y = 2;
x ||= y;console.log(x); // 1

Here line 3 can be expanded like:

x || (x = y)

That means, the assignment operation happens only if x is a falsy value. In our code, x contains 1 which is a truthy value and hence, assignment does not happen. That is why our code prints 1 in the console.

Logical assignment operator with ??

?? is Nullish Coalescing operator in JavaScript. It specifically checks if a value is null or undefined.

var a;
var b = a ?? 5;
console.log(b); // 5

In line 2, if the value of a is null or undefined, the right hand side of ?? is evaluated and assigned to b.

Let us now consider ?? along with =.

var x;
var y = 2;
x ??= y;console.log(x); // 2

Line 2 in the above code is equivalent to:

x = x ?? (x = y)

Here the value of x is undefined. So the right hand side expression is evaluated and sets x to 2.