If JavaScript is part of your dev stack or you have people around working with JavaScript, you would know how powerful and popular JavaScript has become since the release of ES6. ES6 was the biggest release of all time in JS history and a much-needed facelift JavaScript needed to come back to the game. After pushing these many upgrades in one shot, TC39 (Unit of ECMA international which takes care of standards of JavaScript) has decided to push a version out every single year, so it doesn’t become too heavy on developers to get aligned with it. So, all releases have come in subsequent years after that and not to mention they were very small releases compared to ES6.
Let’s see what are those upgrades.
The exponentiation operator (**) returns the result of raising the first operand to the power of the second operand. It is equivalent to Math.pow, except it also accepts BigInts as operands.
2 ** 3 // 2 * 2 * 2 = 8
2 ** 5 // 2*2*2*2*2 = 32
The includes() method determines whether an array includes a certain value among its entries, returning true or false.
[1,2,3,4].includes(2) // true
["q", "w", "e", "r", "t", "y"].includes("John") // false
The async and await keywords enable asynchronous, promise-based behavior to be written in a cleaner style, avoiding the need to explicitly configure promise chains.
Async function comes with one golden rule: It always returns a promise(resolved or rejected). The word “async” before a function makes a function async. Let's look at the syntax.
async function foo(parameter1, parameter2, ...paramaterN) {
// statements
}
// Check this out
async function foo() {
return 1;
}
foo().then(alert); // 1
// We could explicitly return a promise, which would be the same:
async function foo() {
return Promise.resolve(1);
}
foo().then(alert); // 1
The keyword await makes JavaScript wait until that promise settles and returns its result(Trick is, it works only inside of async functions). Let's look at the syntax:
// works only inside async functions
let value = await promise;
// Here’s an example with a promise that resolves in 1 second:
async function foo() {
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("done!"), 1000)
});
let result = await promise; // wait until the promise resolves (*)
alert(result); // "done!"
}
foo();
await literally suspends the function execution until the promise settles, and then resumes it with the promise result. That doesn’t cost any CPU resources, because the JavaScript engine can do other jobs in the meantime: execute other scripts, handle events, etc.
It’s just a more elegant syntax of getting the promise result than promise.then. And, it’s easier to read and write.
// Let's look at a real world scenerio where you would need data from first api call to pass to second and so on:
function fetchData(url, userId, customerId){
// return promise...
}
async function call(){
const result1 = await fetchData(url) // data1 - JS will wait for this to complete to go to next line of execution.
const result2 = await fetchData(url2, result1.id) // data2
const result3 = await fetchData(url3, result1.id, result2.id) // data3
}
call();
The Object.values() method returns an array of a given object's own enumerable property values.
const obj = {name: "Jhon", lastName: "Wick"}
Object.values(obj) // ["John", "Wick"]
The Object.entries() method returns an array of a given object's own enumerable string-keyed property [key, value] pairs.
const obj = {name: "Jhon", lastName: "Wick"}
Object.entries(obj) // [["name", John"], ["lastName", Wick"]]
The Object.getOwnPropertyDescriptors() method returns all own property descriptors of a given object.
const obj = {name: "Jhon", lastName: "Wick"}
const descriptors = Object.getOwnPropertyDescriptors(obj);
console.log(descriptors) // { name: { value: "John", writable: true, enumerable: true, configurable: true } }
console.log(descriptors.name.writable) // true
The padStart() and padEnd() method pads the current string with another string (multiple times, if needed) until the resulting string reaches the given length. The padding is applied from the start of the current string with padStart and from the end with padEnd.
“abc”.padStart(10, 1) // “1111111abc”
“abc”.padStart(10) // “ abc”
“abc.padStart(6, “qwerty”) // “qweabc”
“abc”.padEnd(10, 1) // “abc1111111”
“abc”.padEnd(10) // “abc ”
“abc.padEnd(6, “qwerty”) // “abcqwe”
a variation of the for-of iteration statement which iterates over async iterable objects. Let's look at the example to understand this:
async function fetchJSON(){
const promises = [
fetchJSON(file1.json),
fetchJSON(file2.json),
fetchJSON(file3.json),
fetchJSON(file4.json),
]
// Normal Iterator
for(const x of promises){
console.log(x) // logs promise...
}
// Async Iterator
for await(const x of promises){
console.log(x) // logs a resolved response.
}
}
Rest and Spread go hand in hand. Rest allows us to pass an indefinite number of parameters to a function and access them in an array where The spread operator “spreads” the values in an iterable (arrays, strings) across zero or more arguments or elements.
The Rest/Spread Properties for ECMAScript proposal (ES2018) added Rest properties for object destructuring assignment and Spread properties for object literals. It copies own enumerable properties from a provided object onto a new object.
Let's look at an example to understand how spreading works with an object.
// Rest properties
const {name, ...others} = {name: "John", lastName: "Wick", profession: "BogeyMan"}
console.log(name) // "John"
console.log(others) // {lastName: "Wick", profession: "BogeyMan"}
// Spread properties
const hero = {name, ...others}
console.log(hero) // name: "John", lastName: "Wick", profession: "BogeyMan"}
// This will give TypeError
let obj = {'key1': 'value1'};
let array = [...obj]; // TypeError: obj is not iterable
A finally callback execute logic once your Promise has been settled one way or the other. It has absolutely no impact on the value that your promise will resolve to.
The idea of finally is to set up a handler for performing cleanup/finalizing after the previous operations are complete. E.g. stopping loading indicators, closing no longer needed connections etc.
A finally handler has no arguments. In finally we don’t know whether the promise is successful or not. That’s all right, as our task is usually to perform “general” finalizing procedures and the promise outcome is handled in the next handler.
new Promise((resolve, reject) => {
setTimeout(() => resolve("value"), 2000);
})
.finally(() => alert("Promise ready")) // triggers first
.then(result => alert(result)); // <-- .then shows "value"
The Optional Catch Binding allows us to use the try/catch statement without passing the error parameter inside the catch block. It is optional for you whether you want to pass the error variable or not.
// Wihout optional catch binding
try {
throw new Error("Some random error");
} catch (error) {
console.log(error)
}
// With optional catch binding
try {
throw new Error("Hey");
} catch {
console.log("No parameter for catch");
}
This method is used to transforms a list of key-value pairs into an object. This method returns a new object whose properties are given by the entries of the iterable
const mapArr = new Map(
[['student1', 'Intern'],
['student2', 'Manager']]
);
const person = Object.fromEntries(mapArr);
console.log(person); // Object { student1: "Intern", student2: "Manager" }
The trimStart(alias~trimLeft) and trimEnd(alias~trimRight) method removes whitespace from the string. The trimStart() method removes whitespace from the beginning of a string where trimEnd() method removes whitespace from the end of a string.
const greeting = ' Hello world! ';
console.log(greeting); // " Hello world! ";
console.log(greeting.trimStart()); // "Hello world! ";
console.log(greeting.trimEnd()); // "Hello world!";
The flat() method creates a new array with all sub-array elements concatenated into it recursively up to the specified depth.
Let's look at few examples:
const arr = [1,2,3,[4,5]]
console.log(arr.flat()) // [1, 2, 3, 4, 5]
const arr2 = [1,2,3,[4,5,[6,7,[8,9]]]]
console.log(arr2.flat()) // [1, 2, 3, 4, 5, [6, 7, [8, 9]]]
console.log(arr2.flat(2)) // [1, 2, 3, 4, 5, 6, 7, [8, 9]]
console.log(arr2.flat(3)) // [1, 2, 3, 4, 5, 6, 7, 8, 9]
The flatMap() method returns a new array formed by applying a given callback function to each element of the array, and then flattening the result by one level. It is identical to a map() followed by a flat() of depth 1.
Let's look at few examples:
const arr1 = [1, 2, 3, 4];
arr1.map(x => [x * 2]); // [[2], [4], [6], [8]]
arr1.flatMap(x => [x * 2]); // [2, 4, 6, 8]
// only one level is flattened
arr1.flatMap(x => [[x * 2]]); // [[2], [4], [6], [8]]
JSON.stringify was previously specified to return ill-formed Unicode strings if the input contains any lone surrogates:
JSON.stringify('\uD800'); // → '"�"'
The well-formed JSON.stringify proposal changes JSON.stringify so it outputs escape sequences for lone surrogates, making its output valid Unicode (and representable in UTF-8):
JSON.stringify('\uD800'); // → '"\\ud800"'