DEV Community

Cover image for New Features in ES2021
Marcello La Rocca
Marcello La Rocca

Posted on • Updated on

New Features in ES2021

In this post, we'll take a look at the most interesting features that should be included in ES12, the new version of the standard.

The new feature that will be included in ES12, aka ES2021, the JavaScript specification developed by ECMA for 2021, are currently under discussion, and a few weeks ago, on October 15th, a new draft was released on TC39 website.
You can also check active proposals either here or on GitHub.

String.prototype.replaceAll

This is a convenience method that addresses a specific lack in String.prototype.replace.

Now you can easily replace all occurrences of a given string.

const str = "With great power comes great responsibility";

const str1 = str.replaceAll("great", "little");     // "With little power comes little responsibility"
Enter fullscreen mode Exit fullscreen mode

Notice that both these methods return a new string, without affecting the original variable.

Before replaceAll was introduced, if you had to perform string-replacement you could already use the replace method. Things are fine with replace, unless you need to replace all the occurrences of a pattern with a new string.

For instance:

const str2 = str.replace("great", "little");     // "With little power comes great responsibility"
Enter fullscreen mode Exit fullscreen mode

That's probably not exactly what you meant to say, right?
There is already an alternative way to use replace method and achieve what we wanted: we need to use regular expressions.

const str3 = str.replace(/great/g, "no");     // "With no power comes no responsibility"
Enter fullscreen mode Exit fullscreen mode

Better this time. The catch is, you need to be careful and add the 'g' flag to your RegExp in order to make it a *g*lobal replacement. This means it's error prone, of course, because it's easy to forget it, and you won't notice unless you test your code with a string that needs global replacement.

Is that all? Well, to be honest, there isn't a single catch. Regular expressions are slower, but even worse, sometimes you might not know your pattern in advance:

function fillTemplateVar(template, tag, value) {
    return template.replace(tag, value);
}
Enter fullscreen mode Exit fullscreen mode

The example above shows a typical situation where you would like to replace a pattern in some kind of template, something like <h1>{title}<h1>, were you you'd like to substitute the template-variable title with an actual title: fillTemplateVar('<h1>{title}<h1>', /\{title\}/g, someValue).

But if you need to replace the tags dynamically, and you get them passed as strings, that function wouldn't work unless you use a workaround:

fillTemplateVar('<h1>{title}<h1>', new RegExp(tag, 'g'), someValue)
Enter fullscreen mode Exit fullscreen mode

Using replaceAll, instead, allows you to avoid an unnecessary conversion, and use strings comparison instead of regular expressions matching.

This method is not yet supported in NodeJs, but most browsers already implemented it.

Promise.any

Another method added to the language's toolbelt for handling promises. In ES2020 Promise.allSettled was added to run multiple promises in parallel and act when all of them were settled, either as fulfilled or as rejected.

This new method also takes an iterable (f.i. an array) of promises, but only resolves when either the first one of them is fulfilled, or all of the promises passed are rejected.

Promise.any([get('www.google.com'), get('www.twitter.com')])
    .then(result => {
        console.log('First promise settled: ', result)
    });
Enter fullscreen mode Exit fullscreen mode

So, you might have noticed that this method is quite similar to an existing one, Promise.race, right? But here is the thing with race: it will settle when any of the promises is settled, it doesn't matter if by being rejected or fulfilled. Hence, in those situations where you try multiple solutions and are happy with at least one working and fulfilling its promise, the promise created by race method wouldn't be helpful.

Let's see an example, building an image roulette:

const p1 = new Promise((resolve, reject) => {
  reject("Rejected");
});

const p2 = new Promise((resolve, reject) => {
  setTimeout(resolve, 1500, "Resolved, but slowly");
});

const p3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, "Resolved quickly");
});

// This is resolved, and logs "Resolved quickly"
Promise.any([p1, p2, p3]).then((value) => {
  console.log("Promise.any -> ", value);
});

// This is rejected
Promise.race([p1, p2, p3]).then((value) => {
  console.log("Promise.race -> ", value);
});
Enter fullscreen mode Exit fullscreen mode

There is one question left to discuss: what happens if none of the promises passed is fulfilled?
In that situation (which includes the case where the iterator passed to any is empty), the method throws an AggregateError, a new type of exception contextually introduced in ES2021.

Support is still in development, only some browsers already implemented it, and it's not in NodeJs yet.

Numeric Separators

This is a cosmetic change that might have a low impact on the performance or cleanness of your JavaScript code, but it might help avoiding errors whenever you need to insert numeric values to your code (f.i. while defining constants).
Numeric Separators will make it easier to read these numeric values you define, by letting you use the underscore character _ as a separator between groups of digits.

You can use however many separators you like, and the groups of digits may be of any size - the only limitations are that you can't have two adjacent separators nor you can place them at either end of the number. A few examples will clarify:

const MILLION = 1_000_000;       // 1000000
const BILLION = 1_000_000_000;   // 1000000000

// You can break the digits in any way
const WHATEVER = 1234_5678_9_0;  // 1234567890

// And that's not limited to integers!
const PI = 3.1415_9265_3589;     // 3.141592653589

// Now, do not try this at home! 😁

const BAD_PI = 3.14_15_;          // SyntaxError
const NO_MILLION = _1_000_000;    // ReferenceError! 😱 
      // Remember that variable names can start with underscore... 😉
Enter fullscreen mode Exit fullscreen mode

I don't know how much I'm going to use this feature, but the good news is that it is already supported in most browsers and in NodeJs since version 12.5.0, so we already have a choice.

Intl.ListFormat

Before we delve into this new feature, let's take a step back: the Intl object is the namespace for the ECMAScript Internationalization API, which provides a bunch of helper methods to support internalization efforts, like language-sensitive string comparison, number formatting, and date and time formatting.

In this case, the new constructor ListFormat creates and returns a formatter object that (depending on the configuration passed on creation) will join lists of strings using the best localized conventions.

I realize it's better shown than explained, so let's see an example:

let engFormatter = new Intl.ListFormat('en', { style: 'short', type: 'unit' } );
engFormatter.format(["1","2","3"])   // "1, 2, 3"

let engFormatter = new Intl.ListFormat('en', { style: 'narrow', type: 'unit' } );
engFormatter.format(["1","2","3"])   // "1 2 3"

engFormatter = new Intl.ListFormat('en', { style: 'long', type: 'conjunction' } );
engFormatter.format(["1","2","3"])   // "1, 2, and 3"

engFormatter = new Intl.ListFormat('en', { style: 'long', type: 'disjunction' } );
engFormatter.format(["1","2","3"])   //"1, 2, or 3"
Enter fullscreen mode Exit fullscreen mode

The first optional argument for the ListFormat constructor is the language to be used - 'en' for English in our example. You can also pass an array of these BCP 47 language tags.

The second optional parameter is a POJO with three (also optional) fields:

  • "localeMatcher" sets the locale matching algorithm to be used; it can be either "lookup" or "best fit" (which is the default one).
  • "style", which affects the separators used to join the input strings, and can be either:
    • "long": .format(["1", "2", "3"]) will result in "1, 2, and 3" (assuming that's the only option used).
    • "short": .format(["1", "2", "3"]) should result in "1, 2, 3" (but in Chrome, it outputs "1, 2, & 3");
    • "narrow": .format(["1", "2", "3"]) should result in "1 2 3".
  • "type", regulates the format of the output message; it can be:
    • "conjunction", if we are trying to say that all the items in the list should be included (hence uses "and" before the last item, when style is "long");
    • "disjunction", if we'd like to say that any of the items listed can be included (hence uses "or" before the last item, when style is "long");
    • "unit", which doesn't use any separator for the last string. This option is the only officially valid option when "style" is set to "short" or "narrow".

At least in Chrome's current implementation (version 86), however, the behavior when mixing type and style options is not always the expected one.

New options for Intl.DateTimeFormat: dateStyle and timeStyle

Intl.DateTimeFormat is a constructor for a language-sensitive date and time formatter, long-supported in the JavaScript ecosystem.

These new options allow to control the length of the local-specific formatting of date and time strings.

Let's see how to use it with times...

let formatter = new Intl.DateTimeFormat('en' , { timeStyle: 'short' });
formatter.format(Date.now()); // "12:12 PM"

formatter = new Intl.DateTimeFormat('en' , { timeStyle: 'medium'})
formatter.format(Date.now()) // "12:12:57 PM"

formatter = new Intl.DateTimeFormat('en' , { timeStyle: 'long' })
formatter.format(Date.now()) // "12:12:36 PM GMT-5"
Enter fullscreen mode Exit fullscreen mode

...and dates:

formatter = new Intl.DateTimeFormat('us' , { dateStyle: 'short' });
formatter.format(Date.now()); // "10/27/20"

formatter = new Intl.DateTimeFormat('us' , { dateStyle: 'medium' });
formatter.format(Date.now()); // "Oct 27, 2020"

formatter = new Intl.DateTimeFormat('us' , { dateStyle: 'long' });
formatter.format(Date.now()); // "October 27, 2020"
Enter fullscreen mode Exit fullscreen mode

Obviously you can also combine the two options to get a date-time string:

formatter = new Intl.DateTimeFormat('uk' , { 
    timeStyle: 'long',
    dateStyle: 'short'
});
formatter.format(Date.now()); // "27.10.20, 12:20:54 GMT-5"
Enter fullscreen mode Exit fullscreen mode

Logical Operators and Assignment Expressions

Finally, this new draft is about to make it official some already widely-supported assignment operators like ||= and &&=.

Oldest comments (0)