Introduction
Since I am personally always curious what new features ECMAScript brings with it, I thought I can write a small post that should serve as an overview. Important to be able to really understand individual features especially weakRefs, you have to work through the individual linked proposals. Furthermore, it is a good idea to take a look at MDN Web Docs.
List of new Features
- Logical Assignment Operators
- Numeric Separators
- String.prototype.replaceAll
- Promise any and AggregateError
- Weak refs and FinalizationRegistry
Logical Assignment Operators
https://github.com/tc39/proposal-logical-assignment
For less code and more readability the logical assignment operator was inspired by Rubys Abbreviated Assignment
a ||= 0
a &&= 1
p a # prints 1
In Javascript we will basically get three new assignment operators.
- &&=
- ||=
- ??=
Logical OR assignment (||=)
JS will only assign a new value to the variable, if the old value is falsly
(false, 0, -0, null, undefined, '', ...)
Example
let x = undefined;
// x is undefined so js will assign foo to it
x ||= 'foo';
// x is truthy js will not assign bla to it
x ||= 'bla';
Logical and assignment (&&=)
JS will only assign a new value to the variable, if the old value is truthy ( all values that are not falsly)
Example
let x = undefined;
// x is undefined so js will not assign foo to it
x &&= 'foo';
Logical nullish assignment (??=)
The nullish operator (??) was introduced with ES2020. Unlike falsely values, nullish just means null or undefined.
Other than that the rest of the logic is the same than for falsley values.
JS will only assign a new value to the variable, if the old value is nullish.
Question
To better understand the difference between ||= and ??=.
What will be x and will be y in the example below:
Code
let x = '';
let y = '';
x ??= 'foo';
y ||= 'foo';
Answer
x will still be a empty string, and y will be foo since a empty string is falsely value
Important be aware of short circuit evaluation
It is important to understand that for all new logical assignment operators, the js compiler uses the short circut method. This means for example for the new logical nullish operator that if a value on the left is not nullish, values on the right are not executed. This has advantages especially for functions that may have side effects.
So x ??= y is not the same as x = x ?? y
x ?? (x = y) would be the more equivalent description
Numeric Separators
https://github.com/tc39/proposal-numeric-separator
Numeric Separators are a nice feature for us humans to read numbers better. With an underscore we can press in large numbers better. This also works for binary numbers or hex numbers.
Example
const decimalValue = 666;
const octalValue = 0o12_32;
const hexValue = 0x02_9A;
const binaryValue = 0b0010_1001_1010;
String.prototype.replaceAll
https://github.com/tc39/proposal-string-replaceall
The new native function in javascript string.replaceAll will replace the old hacky method where you had to use a regex to change all chars in a string
Example
const string = 'Ring-ding-ding-ding-dingeringeding';
const withSpacesOld = string.replace(/\-/g, ' ');
const withSpacesNew = string.replaceAll('-', ' ')
Promise.any and AggregateError
https://github.com/tc39/proposal-promise-any
With ES2015, the first two new Promise combinators were introduced. Promise.race and Promise.all. ES2020 introduced Promise.allSettled.
In ES2021, Promise.any is now added. In a simplified way you can imagine that in a block of asynchronous api calls, you are pleased and can continue to work if any of them comes back successful. If none of the api calls in the block returns, the whole block fails. If all of them fail, a new error type is also thrown, namely Aggregate Error.
Example
const onlyRejectedPromises = [
Promise.reject("ERROR everything is a mess"),
Promise.reject("ERROR bla"),
];
const onlyResolvedPromises = [
new Promise((resolve) => {
setTimeout(resolve, 100, "Not to fast");
}),
new Promise((resolve) => {
setTimeout(resolve, 50, "Faster than light");
}),
];
Promise.any([...onlyResolvedPromises, ...onlyRejectedPromises])
.then((value) => {
// faster than light will be printed
console.log(value);
})
.catch((aggregateError) => {
// will not go into the catch
// at last one promise was successful
console.log(aggregateError.errors);
});
Promise.any([...onlyRejectedPromises])
.then((value) => {
// will not go into the then
console.log(value);
})
.catch((aggregateError) => {
// will go into the catch
// ['ERROR everything is a mess', 'ERROR bla']
console.log(aggregateError.errors);
});
WeakRefs and FinlizationRegistry
https://github.com/tc39/proposal-weakrefs
For weakRefs you would probably need a separate blog, because this new feature is very complex and you need to understand exactly how the garbage collector works in Javascript. Therefore I linked a good video which helped me to understand this concept better. In the following I try to explain this new feature with my own words.
Weak JavaScript - HTTP 203
Garbage Collector
To understand weak refs, it is important to first understand how Javascript frees up space when a program has too much memory.
In low level languages like C, the developer has to ensure that a variable that is no longer needed is also wasted from memory.
In JavaScript this is done automatically by the garbage collector.
A big problem for the garbage collector in Javascript is to know if an object which has a reference to the memory is really needed.
Here weakRefs can help.
WeakRefs
A classic use case when it comes to Javascript is of course to store the value of a DOM element into a new variable at runtime. This variable would classically have a strong reference to the dom element. This means in reverse that the garbage collector would never remove the object with the reference from memory. If you just use weakRefs the GC knows that it also has to remove the variable.
Example
The following code shows a code example on a use case for weak reference. source
class Counter {
constructor(element) {
// Remember a weak reference to the DOM element
this.ref = new WeakRef(element);
this.start();
}
start() {
if (this.timer) {
return;
}
this.count = 0;
const tick = () => {
// Get the element from the weak reference, if it still exists
const element = this.ref.deref();
if (element) {
element.textContent = ++this.count;
} else {
// The element doesn't exist anymore
console.log("The element is gone.");
this.stop();
this.ref = null;
}
};
tick();
this.timer = setInterval(tick, 1000);
}
stop() {
if (this.timer) {
clearInterval(this.timer);
this.timer = 0;
}
}
}
const counter = new Counter(document.getElementById("counter"));
setTimeout(() => {
document.getElementById("counter").remove();
}, 5000);
FinalizationRegistry
The FinalizationRegistry object lets you request a callback when an object is garbage-collected.
Example source
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
let waitingForCleanup = true;
const registry = new FinalizationRegistry((heldValue) => {
console.log(`cleanup: ${heldValue}`);
waitingForCleanup = false;
});
let foo = {};
registry.register(foo, 42);
foo = undefined; // Clear strong reference
Important in general weakRefs and FinalizationRegistry should be avoided since GC is not deterministic and you never know if the garbage collector will ever remove the object from memory. So if your code needs optimisation that is depended on weak refs, you should not use it.
Summary
Let's now briefly summarise the new features.
In total there are five new features, three of them I would call syntax sugar (logical assignment, string.prototype.replaceAll(), numeric separators). Promise.any is a continuation and gives us developers more possibilities to display Promise combinations. With weakRefs the normal developer will probably have less contact in his daily use. WeakRefs will surely be a good solution for certain optimisation problems. At the end of the day, they give the developer more tools to help the garbage collector free up even more unneeded memory.
Top comments (0)