Welcome my friend!π
In this post, we are going to explore the use cases of some of the recently released ECMAScript 2021 features, with code examples.
ECMAScript is the scripting language that forms the basis of JavaScript.
The new features:
- String.prototype.ReplaceAll()
- WeakRef
- Finalization Registry
- Promise.any()
- AggregateError
- Logical assignment operators
- Separators for numeric literals
String.prototype.ReplaceAll()
Use case:
This new feature makes it possible to replace every instance of a substring in a string. This is unlike the old String.prototype.Replace() which replaces only the first occurrence of the substring.
Code example:
//with replace()
const replaceMethodEg = "I felt happy because I saw the others were happy and because I knew I should feel happy";
console.log(replaceMethodEg.replace("happy", "sad"));
// Output: I felt sad because I saw the others were happy and because I knew I should feel happy.
//with replaceAll()
const replaceAllMethodEg = "I felt happy because I saw the others were happy and because I knew I should feel happy";
console.log(replaceAllMethodEg.replaceAll("happy", "sad"));
// Output: I felt sad because I saw the others were sad and because I knew I should feel sad.
Cool right? I know!π
WeakRef
A WeakRef lets you create a weak reference to an object, without preventing the object from being reclaimed by the JavaScript engine's garbage collector. The normal(strong) reference to an object keeps an object in memory and prevents the object from being destroyed by garbage collectors. When an object no longer has any strong references to it, the JavaScript engine's garbage collector may destroy the object and reclaim its memory. If that happens, you can't get the object from a weak reference anymore.
Use case:
A primary use for weak references is to implement caches or mappings holding large objects, where itβs desired that a large object is not kept alive solely because it appears in a cache or mapping.
Code Example:
// An object to reference weakly
const objToRef = { name: "Queen", age: 23, favColor: 'purple' };
// creating a WeakRef of this object
const weakRefObj = new WeakRef(objToRef);
To read the value of WeakRefs, use the deref()
method to return the instance
// create an Instance
const weakRefInstance = weakRefObj.deref();
console.log(weakRefInstance.age)
// Output: 23
Warning: Creating a WeakRef is not recommended because of the complicated/unpredictable nature of garbage collectors. When, how, and whether garbage collection occurs is down to the implementation of any given JavaScript engine. Any behaviour observed in one engine may be different from another engine.
Finalization Registry
Finalization is the execution of code to clean up after an object that has become unreachable to program execution.
Use Case:
The Finalization Registry, which was introduced in companion with WeakRef, is used to manage registration and de-registration of cleanup operations performed when target objects are garbage collected. Cleanup callbacks are sometimes called finalizers.
Code Example:
You create the registry passing in the callback:
// create a registry
const registry = new FinalizationRegistry(heldValue => {
// Do something here
});
Then you register any objects you want a cleanup callback for by calling the register()
method, passing in the object and a held value for it:
//register any objects you want a cleanup callback for
registry.register(theObject, "some value");
The object passed into the register()
method will be weak referenced so when the value is garbage collected, the second parameter ('some value') will be passed to the finalizer.
Warning: Just like the WeakRefs, FinalizationRegistry should be avoided and only used after careful thought, for the same reason above.
Promise.any()
Use case:
Promise.any() takes an iterable of promise objects and resolves when one of the promises in the iterable fulfils. This is unlike the old promise.all() which waits for all promises to resolve before it resolves. It short-circuits after a promise fulfils, so it does not wait for the other promises to complete once it finds one. This can be beneficial if we need only one promise to fulfil but we do not care which one does.
Code Example:
// demonstrating promise.any()
const firstPromise = new Promise((resolve) => setTimeout(resolve, 700, 'I am the first promise'));
const secondPromise = new Promise((resolve) => setTimeout(resolve, 300, 'I am the second promise'));
const thirdPromise = new Promise((resolve) => setTimeout(resolve, 1000, 'I am the third promise'));
const allPromises = [firstPromise, secondPromise, thirdPromise];
Promise.any(allPromises)
.then((value) => console.log(value))
// Output: "I am the second promise"
AggregateError
The AggregateError object represents an error that groups together individual errors. It is thrown when multiple errors need to be reported by an operation.
Use case:
Take for instance in the above code for the promise.any()
method, if all the promises reject, an aggregate error is thrown.
Code Example:
// demonstrating AggregateError
const rejectedPromise = Promise.reject(new Error("an error"));
Promise.any([rejectedPromise])
.catch(e => {
console.log(e instanceof AggregateError); // Output: true
console.log(e); // Output: AggregateError: All promises were rejected
});
Logical assignment operators
This is a combination of the logical operations (&&, || or ??)
with the assignment operator (=)
.
ie ||=, &&=, ??=
.
Logical OR assignment operator ||=
Use case:
The logical OR assignment x ||= y
operator only assigns if x is falsy.
Code Examples:
// Logical OR assignment operator (||=)
let myName = '';
let myGrade = 5;
myName ||= 'Obi';
console.log(myName); // Output: "Obi"
myGrade ||= 2;
console.log(myGrade); // Output: 5
Logical AND assignment operator &&=
Use case:
The logical AND assignment x &&= y
operator only assigns if x is truthy.
Code Example:
// Logical AND assignment (&&=)
let a = 1;
a &&= 3 // Output: a = 3
Logical nullish assignment ??=
Use case:
The logical nullish assignment x ??= y
operator only assigns if x is nullish (null or undefined).
Code Examples:
// Logical nullish assignment (??=)
let a;
let b = 50;
a ??= b;
console.log(a); //Output: 50
Separators for numeric literals
Use case:
These separators were introduced to enable developers to make numeric literals more readable by creating a visual separation between groups of digits. Large numeric literals are difficult to parse visually, particularly when there are long digit repetitions.
1000000000 // Is this a billion? a hundred million? Ten millions?
101475938.38 // what scale is this? what power of 10?
Code Examples:
Regular Number Literals:
let budget = 1_000_000_000_000;
// What is the value of `budget`? It's 1 trillion!
//
// Let's confirm:
console.log(budget === 10 ** 12); // true
Binary Literals:
let nibbles = 0b1010_0001_1000_0101;
// Is bit 7 on? It sure is!
// 0b1010_0001_1000_0101
// ^
//
// We can double check:
console.log(!!(nibbles & (1 << 7))); // true
Hex Literal:
// Messages are sent as 24 bit values, but should be
// treated as 3 distinct bytes:
let message = 0xA0_B0_C0;
// What's the value of the upper most byte? It's A0, or 160.
// We can confirm that:
let a = (message >> 16) & 0xFF;
console.log(a.toString(16), a); // a0, 160
// What's the value of the middle byte? It's B0, or 176.
// Let's just make sure...
let b = (message >> 8) & 0xFF;
console.log(b.toString(16), b); // b0, 176
// What's the value of the lower most byte? It's C0, or 192.
// Again, let's prove that:
let c = message & 0xFF;
console.log(c.toString(16), b); // c0, 192
Let me know what you think about the features.
Happy coding!πͺ