Backbencher.dev

ES2021 / ES12 New Features

Last updated on 11 Jun, 2021

Here are the new features that got added in ES12.

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.

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

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.

Underscores as Numeric Seperator

Is 1000000000, one billion? Are you finding difficult to count the number of zeros? If it is difficult to work with, ES2021 supports _ which can be placed as numeric separator. When one billion can be written as 1000_000_000 in code, it is way easier to read.

const billion = 1000_000_000;
console.log(billion); // 1000000000

The underscore(_) separator also works with BigInt numbers.

const trillion = 1000_000_000_000n;
console.log(trillion.toString()); // "1000000000000"

The separator is just for readability purpose. So, it can be placed anywhere within the number.

const amount = 178_00; // 00 after _ for cents.
--- ○ ---
Joby Joseph
Web Architect