Vehid Trtak

Vehid Trtak

en-US

New ES2021 Javascript features

. . .
image

NOTE: All these ES2021 features are available in most of the major browsers, others are in process of adding them. So you can try all these examples in any major browser, but I recommend Brave and Google Chrome

Before we start just to mention that even if these features are added to the browser for us to use, as of the day I'm writing this blog April 24, 2021, these features are not added to ECMAScript and are not part of Javascript standard. Approval is scheduled for June 2021, and then they will become part of the standard. If you want to see about these features or any new one, you can go to the official GitHub repository where these features get presented, discussed, and approved for the addition to Javascript TC39.

We have five new features in Javascript, and they are:

  • String.prototype.replaceAll()
  • Promise.any()
  • Logical Assignment Operators
  • Numeric spearators
  • WeakRefs

String.prototype.replaceAll()

The first one is replaceAll() that we can use to replace all instances in string. We already had replace() which does a similar thing but replace() only replaces the first instance in a string and not all of them. The workaround for replacing all instances was by using regex. As we know regex is confusing, the syntax is kind of weird. Also, since it's not used that often we forget how to work with regex so we have to refresh our mind every time we need it, or just copy the solution without knowing why is written like that (which is a bad practice, you should know what the stuff you are using is doing in code). So this addition is quite useful and we can replace instances in a string without needing to mess with regex. Here is an example of how you can use replaceAll():

1const text = 'This text should be replaced with a different text';
2
3const replaceFn = text.replace('text', 'name');
4
5const replaceAllFn = text.replaceAll('text', 'name');
6
7console.log(replaceFn);
8// This name should be replaced with a different text
9
10console.log(replaceAllFn);
11// This name should be replaced with a different name

If you try this example above for yourself, you'll see that replaceFn is only replacing the first instance of word "text", but replaceAllFn is replacing all instances of word "text". As you can see this new feature is nice and useful, no more messing with regex, at least for this use case 🎉. If you want to check more about this feature you can go on the offical GitHub repository where these features are presented and discussed String.prototype.replaceAll().

Promise.any()

The second was added to the Promise methods, and it receives a list of promises Promise.any([first, second, third]) and we wait for the first successful or all of them to fail. So if the first promise is successful we abort others, but if first is a failure and second one is successful then it gets aborted. The same goes for if first and second are failures but the third is successful. This is a kinda optimistic Promise that will not stop until one is successful or all of them fail. Now let me show you an example of how to use it, and how it's different from the other methods:

1// We use delay to simulate response from an API
2const delay = () => Math.floor(Math.random() * 1000);
3
4const first = new Promise((resolve, reject) => {
5 setTimeout(() => resolve('First'), delay());
6});
7
8const second = new Promise((resolve, reject) => {
9 setTimeout(() => resolve('Second'), delay());
10});
11
12const third = new Promise((resolve, reject) => {
13 setTimeout(() => resolve('Third'), delay());
14});
15
16const promiseAny = async () => {
17 try {
18 const firstResponse = await Promise.any([first, second, third]);
19
20 //First one to be successfull will be the value of firstResponse
21 console.log(firstResponse);
22 } catch (error) {
23 console.log(error);
24 }
25};
26
27promiseAny();

In this example, we have all the promises resolving, and the first one to respond is the one that will be assigned to firstResponse. But what if one of them rejects or maybe two of them reject? Then we will get the one that resolved:

1// We use delay to simulate response from an API
2const delay = () => Math.floor(Math.random() * 1000);
3
4const first = new Promise((resolve, reject) => {
5 setTimeout(() => reject('First'), delay());
6});
7
8const second = new Promise((resolve, reject) => {
9 setTimeout(() => reject('Second'), delay());
10});
11
12const third = new Promise((resolve, reject) => {
13 setTimeout(() => resolve('Third'), delay());
14});
15
16const promiseAny = async () => {
17 try {
18 const firstResponse = await Promise.any([first, second, third]);
19
20 //Third one will be the value of firstResponse
21 console.log(firstResponse);
22 } catch (error) {
23 console.log(error);
24 }
25};
26
27promiseAny();

Here we have first and second promise being rejected. The third is getting resolved, that's why we will always get third as our response from Promise. And if all of them were rejected, we would get an error that says AggregateError: All promises were rejected. Here is a code example of that:

1// We use delay to simulate response from an API
2const delay = () => Math.floor(Math.random() * 1000);
3
4const first = new Promise((resolve, reject) => {
5 setTimeout(() => reject('First'), delay());
6});
7
8const second = new Promise((resolve, reject) => {
9 setTimeout(() => reject('Second'), delay());
10});
11
12const third = new Promise((resolve, reject) => {
13 setTimeout(() => reject('Third'), delay());
14});
15
16const promiseAny = async () => {
17 try {
18 const firstResponse = await Promise.any([first, second, third]);
19
20 //This will not be executed since our Promise throws error
21 console.log(firstResponse);
22 } catch (error) {
23 // Here we will get our error: AggregateError: All promises were rejected
24 console.log(error);
25 }
26};
27
28promiseAny();

Now let's see how Promise.any() compares to other Promise methods:

1// Here we wait for all promises to finish but if one fails, we throw an error
2Promise.all([first, second, third]);
3
4// Here we wait for first promise to finish, failed or sucessfull
5Promise.race([first, second, third]);
6
7// Here we wait for all promises to finish, and array of all promises is returned
8// with their status and values or reason if it's rejected
9Promise.allSettled([first, second, third]);
10
11//Here we wait for the first that is succefull or all of them failed
12Promise.all([first, second, third]);

And here is where you can read more about this feature Promise.any()

Logical Assignment Operators

This feature was inspired by the Ruby programming language, and we use it to write conditional checks in shorter forms. There is not a lot to explain here since we already had these, but this is just a shorter version. Here is an example of usage:

1// x = x || 0
2// x ||= 0
3// x = x ?? 0
4// x ??= 0
5// x = x && 0
6// x &&= 0
7
8const multiply = (dot) => {
9 dot.x ??= 2;
10 dot.y ??= 2;
11 return dot;
12};
13multiply({ x: 0 }); // 4

So for dot.x ??= 2 , if dot.x is not null or undefiend we will leave it's value assigned to him, but if it's null or undefiend we will assign 2. This sign ?? is called Nullish Coalescing operator and it specifically checks if the value is null or `undefined. It was added in ES2020 to the Javascript.

If you want to see more about this feature go to the official GitHub repo Logical Assignment Operators.

Numeric separators

This feature was added to improve the readability of numbers. So if you write a large number it's hard to see, so you can use this to separate hundreds, thousands, etc. Here is an example usage of this:

1const largeNumer = 12000000000;
2
3const largeNumberFormated = 12_000_000_000;

You can see that it helps read if this is millions, billions, or trillions. Go to the GitHub repo to read more about it Numeric separators

WeakRefs

Before I say anything about this feature, I have to say that possibility of us needing to use this is quite small. This is a bit higher level Javascript, but I'll try to explain anyway, who knows maybe we'll use it one day 🤣.

In Javascript, we have Garbage Collectors(GC), which means that the Javascript engine cleans unused values so it can free memory and set something else in that place. So if we have a variable let person that is referencing an object that means that we have a strong reference to that object, and as long something has a reference to that object, that object will not get Garbage Collected which means it will not get removed from memory.

So if we have an object in memory that no one is referencing, there is a possibility that it will get GC. And why I say maybe is because GC is non-obvious in Javascript which means that it is not certain when GC will happen and if it will happen at all. And also GC work differently on different Javascript engines, so Google Chrome V8 will have a different GC from Firefox that is using Spider monkey engine.

NOTE: In Javascript, only Objects and Strings are Garbage Collected

WeakRefs are weak references to the objects in memory, and that means that we are not strongly pointing to that object and if GC happens we will not point to anything anymore, and the variable that we assigned WeakRef to will be undefined. We can check if WeakRef has value by calling deref() and if we get undefiend that means that object was removed by GC, and if we get a value means an object is still in memory and we can use it. Here is an example of how we can do that:

1const value = {
2 name: 'Vehid Trtak',
3 city: 'Sarajevo'
4};
5
6//Create weak reference to the value
7const weakRef = new WeakRef(value);
8
9//Check if values still exists
10const maybeValue = weakRef.deref();
11if (maybeValue === undefiend) {
12 // Value was removed by GC
13} else {
14 // Value is still available
15}

There is also one more thing that can be used with WeakRefs and its FinalizationRegistry. This is a way for us to get notified when our value is Garbage Collected, so we don't have to check deref() all the time to see if it still exists. And here is code showing how to use it:

1const notify = new FinalizationRegistry((value) => {
2 // Do something when you get notified
3});

NOTE: When FinalizationRegistry callback is executed that means that values are already removed and the object doesn't exist in memory anymore!

Since this is a complex topic, and even people that propose this to be added to Javascript are telling that you probably shouldn't use this unless you know what you are doing I won't spend any more time, if you want to check more check here WeakRefs and FinalizationRegistry.

Conclusion

We got through these five new additions, some of them useful and some of them not so much. Hope you found this useful, and if you did share the knowledge so others can learn about it too. And check the official GitHub repository and see what else is there, maybe there is good stuff from previous Javascript updates, or maybe to check things that are planned for future releases like Records and Tuples.