ES 2021 - Meet the New Additions to the Browser Near You

ES 2021 - Meet the New Additions to the Browser Near You

ES 2021 got standardized few days back(22nd June), and it now supports few really exciting features that contribute towards improving the readability of the language syntax as well as add to the construct repertoire available to deal with string replacement, asynchronicity, and performance.

Let's see what these new additions are!

String replaceAll()

or, one method to replace `em all! 💪

Javascript has long needed a way to replace all the occurrences of a string with provided string, and this method does exactly what we already expect by its name.

Although, javascript has a String.prototype.replace method it can only replace the first occurrence.

const str = "Mandalorian knows Mandorian, but Mandorians dont know Mandalorian";
const updatedStr = str.replace("Mandalorian", "Bobba")

console.log(updatedStr); // Bobba knows Mandorian, but Mandorian dont know Mandalorian

If we need to carry out full replacement we'll have to express the match value as a regular expression

const str = "Mandalorian knows Mandorian, but Mandorians dont know Mandalorian";
const updatedStr = str.replace(/Mandalorian/g, "Bobba")

console.log(updatedStr); // Bobba knows Mandorian, but Mandorians dont know Bobba

With String.prototype.replaceAll we now can carry out a full replacement without relying on regex patterns

const str = "Mandalorian knows Mandorian, but Mandorians dont know Mandalorian";
const updatedStr = str.replaceAll("Mandalorian", "Bobba")

console.log(updatedStr); // Bobba knows Mandorian, but Mandorians dont know Bobba

Promise's Promise.any

or, just gimme what resolves first! 💨

Promise.any is polar opposite of Promise.all, and it prioritizes the promise that resolves first, and could compensate for latency in scenarios with multiple independent parts where the success of one participant promise is all that's needed. Unlike Promise.all it does not waits for the fulfilment of all participating promises or first rejection to resolve to let the dependent code act. For the following promises that resolve at different times.

const fetchTheMando = new Promise((resolve, reject) => {
  setTimeout(
    () => resolve("The Mandalorian is here now!"),
    Math.floor(Math.random() * 100)
  );
});
const fetchBobba = new Promise((resolve, reject) => {
  setTimeout(
    () => resolve("Bobba Fett is here now!"),
    Math.floor(Math.random() * 100)
  );
});
const fetchLuke = new Promise((resolve, reject) => {
  setTimeout(
    () => resolve("Luke Skywalker is here now!"),
    Math.floor(Math.random() * 100)
  );
});

it could be simply used like

(async function() {
  // Fetch one and save the child!
  const result = await Promise.any([ fetchTheMando, fetchBobba, fetchLuke ]);
  console.log(result); // Prints "The Mandalorian/Boba Fett/Luke Skywalker is here now!" for whatever promise resolves first
})();

When any of the participant promises fail, you get a AggregateError that could be handled in a try/catch block like other exceptions.


const fetchTheMando = new Promise((resolve, reject) => {
  setTimeout(
    () => resolve("The Mandalorian is here now!"),
    Math.floor(Math.random() * 100)
  );
});

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

try {
  (async function() {
    const result = await Promise.any([ fetchTheMando, fetchMoff ]);
  })();
} catch(error) {
  console.log(error.errors);
}
  • Promise.any is similar to Promise.race, except it, doesn’t reject early when one of the promises rejects.

Logical Assignment Operator

or, I ❤️ shorter short-cuts!

If expressing a condition like x && x = y or if (x) { x = y } feels verbose, repetitive, or tiring to you the logical assignment operators will prove to be a good friend. You can express the aforementioned expressions simply as x &&= y now. The new syntax combines the logical operations(&&, || or ??) with assignment.

What's more amazing is that this feature extends beyond logical and && to logical OR || and the Nullish Coalescing operator ?? too. Let's see how these could be applied.

Logical AND &&

let x = 1;
let y = 2;

// pre-ES 2021
x = x && y

// Now
x &&= y; // x will be 2

Logical OR ||

let x = 1;
let y = 2;

// pre-ES 2021
x = x || y;

// now...
x ||= y; // x will be 2 as the assignment will only happen when x is falsy

Null Coalescing ??


let x;
const y = 2;

// pre-ES 2021
x = x ?? y;

// now...
x ??= y; // as x is undefined, x will be 5 post this

Numeric Separators

or, I luv 🔢 neat!

Underscore(_) as separators has been available in many programming languages, and can greatly improves the readability in representing numeric and bigint values. Here's how you can use it

// pre-ES 2021
const bounty = 1000000000; // Is this a billion? a hundred million? Ten million?

// now...
const bounty = 1_000_000_000_000; // neat!

this can be applied to binary

let beskar = 0b1010_0001_1000_0101;

and hex literals too

let messageFromMarshal = 0xA0_B0_C0;

WeakRef and FinalizationRegistry

or, I like being 🧠 mindful towards memory!

WeakRef and FinalizationRegistry serve two functions

  • creation of weak references to objects with the WeakRef class
  • running user-defined finalizers after objects are garbage-collected

and could be used independently or together, depending on the use-case.

A WeakRef (weak reference) to an object is a reference that won't prevent the object it refers to, from being garbage collected. Even, when garbage collector sees the referred object referenced in a non-collectible path.

Consider the following code

const mandoCalls = () => {
  const starWarsGalaxy = {
    entities: {
            mando: {
                title: "lone bounty hunter",
                bio: "travels in the outer reaches of the galaxy, far from the authority of the New Republic"
            },
            ...
        },
        travels: {
            ...
        }
  };
  console.log(starWarsGalaxy);
}

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

When the above code is executed, starWarsGalaxy will be printed after 2 seconds. Based on how we use the mandoCalls , starWarsGalaxy may remain stored in memory forever.

A WeakRef will let you declare this object as a weak reference, and makes it ephemeral in a non-predictable world.

const mandoCalls = () => {
  const starWarsGalaxy = new WeakRef({
    entities: {
            ...
        },
        locations: {
            ...
        }
  });
  console.log(starWarsGalaxy.deref());
}

(async function(){
  await new Promise((resolve) => {
    setTimeout(() => {
      mandoCalls(); // Guaranteed to print starWarsGalaxy entries
      resolve();
    }, 2000);
  });

  await new Promise((resolve) => {
    setTimeout(() => {
      mandoCalls(); // No Gaurantee that starWarsGalaxy is still attached
      resolve();
    }, 4000);
  });
})();

Use of the target or referent object starWarsGalaxy is guaranteed to work as expected in the first mandoCalls but has zero chances of yielding in the subsequent calls. As referents are wrapped in a WeakRef , their access approach also changes to referent.deref() instead of direct access.

WeakRef prove useful in implementing cache or mappings to large objects where you'd not prefer to hold the referent objects too long after use.

FinalizationRegistry as a companion feature lets you listen to garbage collection on objects you're interested in.

Once you have a FinalizationRegistry instance, with a cleanup callback

const registry = new FinalizationRegistry((heldValue) => {
  // do something with the held value
});

You can register the objects you'd like the callback to be executed for (post garbage collection) with the register method

registry.register(theInterestingObject, "pass this on as the held value!");

Execute something like

(function () {
  const starWarsGalaxy = {};

  registry.register(starWarsGalaxy, "Bobba says bye");
})();

and the passed message Bobba says bye will be received by the cleanup callback, registered while instantiating FinalizationRegistry as soon as starWarsGalaxy is garbage collected.

Conclusion

You might not find immediate use for these new additions, but they're noteworthy enhancements from a language maturity point of view. All of these features are available to the evergreen browsers near you, and could be easily configured with babel for use.