The 5 most transformative JavaScript features from ES12

Last updated on August 24, 2024
The 5 most transformative JavaScript features from ES12

ES12 was truly an amazing upgrade.

Packed with valuable features that completely transformed the way we write JavaScript.

Code became cleaner, shorter, and easier to write.

Let’s check them out and see the ones you missed.

1. Promise.any()

Before ES12, we already had Promise.all() and Promise.allSettled() to wait for an entire group of Promises.

There were several times when we'd have several Promises but only be interested in whichever one resolved first.

So Promise.any() had to come into the picture:

async function getHelpQuickly() {
  const response = await Promise.any([
    cautiousHelper(),
    kindHelper(),
    wickedHelper(),
  ]);
  console.log(response); // Of course!
}

async function cautiousHelper() {
  await new Promise((resolve) => {
    setTimeout(() => {
      resolve('Uum, oohkaay?');
    }, 2000);
  });
}

async function kindHelper() {
  return 'Of course!';
}

function wickedHelper() {
  return Promise.reject('Never, ha ha ha!!!');
}

// codingbeautydev.com

One interesting thing to note: even though any() resolves immediately, the app doesn't end until all the Promises have resolved.

2. replaceAll()

Yes we already had replace() for quickly replace a substring within a string.

const str =
  'JavaScript is so terrible, it is unbelievably terrible!!';

const result = str.replace('terrible', 'wonderful');

console.log(result);
// JavaScript is so wonderful, it is unbelievably terrible!!
// Huh?

// codingbeautydev.com

But it only did so for the first occurrence of the substring unless you use a regex.

So ES12 gave us now we have replaceAll() to replace every single instance of that substring.

const str =
  'JavaScript is so terrible, it is unbelievably terrible.';

const result = str.replaceAll('terrible', 'wonderful');

console.log(result);

// JavaScript is wonderful, it is unbelievably wonderful.
// Now you're making sense!

// codingbeautydev.com

3. WeakRefs

As from ES12, a JavaScript variable can either be a strong reference or a weak reference.

What are these?

The Strong Refs are our normal everyday variables. But the Weak Refs need to be explicitly created with WeakRef():

const strongRef = { name: 'Tari Ibaba' }

const weakRef = new WeakRef(strongRef);

// codingbeautydev.com

JS variables are just references to the actual object in memory.

That's why we can have multiple references to the same object.

const person = { name: 'Tari Ibaba' };

const me = person;

// modifies the actual object that `me` points to
person.site = 'codingbeautydev.com';

console.log(me.site); // codingbeautydev.com

But what's the difference between a strong ref and a weak ref?

Well in programming we have something garbage collection, which is when unneeded objects are removed from memory to save resources.

In JavaScript, objects are automatically marked for garbage collected when all the strong ref variables pointing to it have become unreachable -- out of scope:

The object person and me both point to is put on the destruction queue once func() runs.

func();
// 💡`person` and `me` are unreachable here

function func() {
  // 💡this object will be marked for garbage collection
  // after this function runs
  const person = { name: 'Tari Ibaba' };

  const me = person;

  person.site = 'codingbeautydev.com';

  console.log(me.site); // codingbeautydev.com
}

But look what happens here:

Even person went out of scope after func() finished, we still had me, a global strong reference.

let me;

func();
// 💡one strong reference (`me`) is still reachable

// ✅ Can always access object
console.log(me.site);

function func() {
  // 💡this object will NOT be garbage collected
  // after this function runs
  const person = { name: 'Tari Ibaba' };

  me = person;

  person.site = 'codingbeautydev.com';

  console.log(me.site); // codingbeautydev.com
}

But what if me was a weak reference?

Now after func() executes, person would be the only strong reference to the object.

So the object will be marked for garbage collection:

let me;

func();
// No strong references reachable

// ❌ Bad idea: object may not exist
console.log(me.deref().site);

function func() {
  // 💡this object will be marked for garbage collection
  const person = { name: 'Tari Ibaba' };

  me = new WeakRef(person);

  person.site = 'codingbeautydev.com';

  console.log(me.deref().site); // codingbeautydev.com
}

So why do we need weak references?

The biggest use case for them is caching.

Look what happens here: processData() runs and we have a new object stored in our cache.

Even though data becomes unreachable, the object will never be garbage collected because it has a strong reference in the cache.

let cache = new Map();

processData();

function processData() {
  const url = 'api.tariibaba.com';
  const data = fetchData(url);
  // process data for app...
}

async function fetchData(url) {
  // check cache
  const saved = cache.get(url);
  if (!saved) {
    const data = await (await fetch(url)).json();
    cache.set(url, data);
  }
  return saved;
}

But what if I want the object to be freed up after processData() exits?

I would use a WeakRef as the Map values instead:

let cache = new Map();

processData();

function processData() {
  const url = 'api.tariibaba.com';
  const data = fetchData(url);
  // process data for app...

  // 💡the object will only exist in cache
  // in this function
}

async function fetchData(url) {
  // deref weak ref
  const saved = cache.get(url).deref();
  if (!saved) {
    const data = await (await fetch(url)).json();
    // ✅ Use a WeakRef instead
    cache.set(url, new WeakRef(data));
  }
  return saved;
}

4. Logical assignment operators

Lovely syntactic sugar from ES12.

We use them like this:

left ??= right;
left ||= right;
left &&= right;

// codingbeautydev.com

Exactly the same as:

left = (left ?? right);
left = (left || right);
left = (left && right);

// codingbeautydev.com

??=. Quickly assign a value to a variable *if* it is null or undefined (“nullish”).

user.preferredName ??= generateDumbUserName();

||=. Like ??=, but assigns the value for any falsy value (0undefinednull''NaN, or false).

user.profilePicture ||= "/angry-stranger.png";

And then &&=. Something like the reverse; only assigns when the value is truthy (not falsy).

user.favoriteLanguage = await setFavoriteLanguage(input.value);

user.favoriteLanguage &&= 'Assembly'; // You're lying! It's Assembly!

5. Numeric separators

Tiny but impactful new addition that made big number literals more readable and human-friendly:

const isItPi = 3.1_415_926_535;

const isItAvagadro = 602_214_076_000_000_000_000_000;

const isItPlanck = 6.626_070_15e-34;

const isItG = 6.674_30e-11;

// Works for other number bases too...

// codingbeautydev.com

The compiler completely ignores those pesky underscores — they’re all for you, the human!

Final thoughts

These are the juicy new JavaScript features that arrived in the ES12.

Use them to boost your productivity as a developer and write cleaner code with greater conciseness, expressiveness and clarity.

Coding Beauty Assistant logo

Try Coding Beauty AI Assistant for VS Code

Meet the new intelligent assistant: tailored to optimize your work efficiency with lightning-fast code completions, intuitive AI chat + web search, reliable human expert help, and more.

See also