8 great new JavaScript language features in ES12

ECMAScript 2021 highlights include replaceAll(), promise.any(), AggregateError, and new logical assignment operators, for starters. Let’s dive in.

8 great new JavaScript language features in ES12
TNS Sofres (CC BY 2.0)

The JavaScript language specification, also known as ECMAScript or ES, is a living document, modified every year in response to evolving needs. While JavaScript started as a scripting language, the ECMAScript specification overview notes that the language “is now used for the full spectrum of programming tasks in many different environments and scales." Because of this, JavaScript is better understood as a fully featured general-purpose programming language.

With the upcoming ECMAScript 2022 release just around the corner, let's take a look at the new JavaScript language features introduced in ECMAScript 2021.

String.prototype.replaceAll

The replaceAll() method takes a string or regular expression, called the pattern, as its first argument. The second argument is the pattern's replacement. Given the first and second argument, replaceAll() returns a new string that will be the source string with all instances of the pattern swapped for the replacement. The source string is not affected.

In ECMAScript 2021, replaceAll() joins ECMAScript 2020’s matchAll() in improving the inherent capabilities of JavaScript’s built-in String object.

The replaceAll() method works exactly like replace(), but applies to all occurrences in the string, instead of just the first one. It’s a welcome addition after years of having to use a library or hand-coded solution.

Listing 1 shows a simple example, wherein we mangle some Shakespeare.

Listing 1. replaceAll()


let quote = "all the world's a stage, and all the men and women merely players";
let newQuote = quote.replaceAll("all", "most of");
console.log(newQuote);

promise.any()

The promise.any() method takes a collection of promises and allows you to respond to the first one that completes successfully by returning a new promise.

If any promises are rejected, they are ignored. (Note this method's contrast with promise.all(), which stops with any error or rejection; and with promise.allSettled(), which lets you observe all promises that resolved in a collection, even if there were intervening errors.)

If any of the promises error out, promise.any() will still act upon the first resolved promise in the collection.

The promise.any() method returns a rejected promise if none of the passed-in promises resolves. The error it returns is AggregateError, which is a new error type also introduced by ECMAScript 2021. AggregateError represents the summary of all errors encountered.

You can use promise.any() to roll up many promises into a single one. This promise will resolve to whichever of the collection resolves first, ignoring errors and rejections.  Listing 2 has a simple example.

Listing 2. promise.any()—all resolved


const promise1 = new Promise((resolve, reject) => {
  setTimeout(resolve, 1000, "1 second");
});

const promise2 = new Promise((resolve, reject) => {
  setTimeout(resolve, 2000, "2 second");
});

let promises = [promise1, promise2];

Promise.any(promises).then((firstResolved) => {
  console.log(firstResolved); // outputs “1 second”
})

Now consider Listing 3, wherein all promises eventually fail as rejected.

Listing 3. promise.any()—all rejected


const promise1 = new Promise((resolve, reject) => {
  setTimeout(reject, 1000, "1 second");
});

const promise2 = new Promise((resolve, reject) => {
  setTimeout(reject, 2000, "2 second");
});

let promises = [promise1, promise2];

Promise.any(promises).then((firstResolved) => {
  console.log(firstResolved);
}).catch((err) => { console.log("error: " + err) }) // outputs error: AggregateError: All promises were rejected

In Listing 3, we add a catch handler, which fires after both promises are rejected. Notice that AggregateError is an object holding information about the failed promises. 

Let's get a closer look at AggregateError, which is another new feature in ECMAScript 2021.

AggregateError

AggregateError is a special kind of error subclass that combines many errors into a summary object. As you saw, the promise.any() method in Listing 3 created an AggregateError.

In that example, all promises passed to promise.any() failed, so the method returned an AggregateError. The error contained a message describing the error and an array with details about each error. Listing 4 shows the contents of the error returned.

Listing 4. AggregateError


AggregateError: All promises were rejected
  errors: Array(2)
    0: "1 second"
    1: "2 second"
      length: 2
  message: "All promises were rejected"
  stack: "AggregateError: All promises were rejected"

As shown, AggregateError gives you access to the promise messages that contributed to the error, via AggregateError.errors.

New logical assignment operators

JavaScript has familiar math assignment operators such as +=, which perform the operation and assignment in one go, as a kind of convenience. ECMAScript 2021 adds similar support to the logical operators ||, ??, and &&.

Let’s take a look at each of these.

Nullish assignment (??=)

You can use the nullish assignment operator to test a variable for being null or undefined. If the variable is null or undefined, you can assign the right side of the expression to the variable. Listing 5 is an example.

Listing 5. The ??= assignment in action


let quote = "When goodness is lost there is morality.";
let existingQuote = "A leader is best when people barely know he exists";
let nonExistingQuote = null;
existingQuote ??= quote;
nonExistingQuote ??= quote;
console.log(existingQuote); // A leader is best when people barely know he exists
console.log(nonExistingQuote); // When goodness is lost there is morality.

Notice that when used on a variable that exists, like existingQuote, the nullish assignment operator does nothing. When used on nonExistingQuote, however, it assigns the quote a new value. 

Even if the string were empty for existingQuote (which is a falsy value in JavaScript), the nullish assignment will not replace it; it will remain an empty string.  That is the essence of the operator: it only tests for null or undefined.

And assignment (&&=)

The and assignment operator (&&=) tests the left side of an expression. If the left side is truthy, it assigns the right side of the expression. If it is falsy, the operator does nothing. Listing 6 shows a simple example.

Listing 6. The &&= assignment in action


let emptyString = "";
emptyString &&= "bar";
console.log (emptyString); // “”

let nonEmptyString = "foo";
nonEmptyString &&= "bar";
console.log(nonEmptyString); // “bar”

In Listing 6, the first console log outputs an empty string. This is because the empty string is falsy, so the &&= operator does not assign it a new value. The second console outputs "bar". This is because nonEmptyString is “foo”, which is a truthy value.

&&= is a kind of edge case operator, but useful when you need it.

Or assignment (||=)

The or assignment operator is the opposite of the and assignment operator you just saw. We can use the same example from Listing 6, this time replacing &&= with ||=.

Listing 7. The ||= assignment in action


let emptyString = "";
emptyString ||= "bar";
console.log (emptyString); // “bar”

let nonEmptyString = "foo";
nonEmptyString ||= "bar";
console.log(nonEmptyString); // “foo”

If the left side of the expression is falsy, the ||= assignment operator resolves to the right side. Therefore, in this case, the emptyString becomes “bar”. The nonEmptyString variable stays with its truthy value of “foo”.

WeakRef

WeakRef is used to refer to a target object without preserving it from garbage collection. It is a rather esoteric language feature, not much used by the working coder. The one common use case for WeakRef is in implementing caches or mappings for large objects, "where it’s desired that a large object is not kept alive solely because it appears in a cache or mapping.”

So, if you find yourself building a caching solution for large entities, remember that WeakRef exists. Otherwise, if you are not sure about needing a WeakRef variable reference, you probably should avoid using it. (The spec itself recommends  avoidance.)

FinalizationRegistry

It is a bit of programming irony that JavaScript introduced FinalizationRegistry almost simultaneously with Java’s deprecation of Object.finalize(). The features are practically analogous. Like WeakRef, the specification warns developers away from user-defined finalizers

For some use cases, however, the new FinalizationRegistry could be just what you need. The specification offers the example of long-running processes consuming many file handles. In such a case, using FinalizationRegistry could ensure no handles are leaked.

Along with WeakRef, FinalizationRegistry fits better into the toolbox of platform and framework developers, rather than application developers.

Numeric literal separators

Numeric separators are a nicety that make looking at large number easier on the eyes. JavaScript can’t use commas like natural languages, because that symbol is already taken. So, ECMAScript 2021 introduced the underscore.

Instead of typing


let distanceToSun = 91772000000;

you can type


let distanceToSun = 91_772_000_000;

The new form is quite a bit easier to read.

Array.prototype.sort improvements

This is more of a note than a feature. Basically, the ECMAScript 2021 spec is more exact in its description of how Array.prototype.sort works. The change should lessen variations in implementation between engines.

Copyright © 2022 IDG Communications, Inc.