Unlock the door for ES2018/ES2019!
No one can deny how much JavaScript has became one of the most popular programming languages used by developers across the globe not only for the web but also for desktop and mobile applications due to it’s flexibility, performance and a huge community which stands behind it.
In this article we’ll try to take a quick overview about some of the new ES2018/ES2019 features which already approved by TC39(for those who doesn’t know what is TC39, is the technical committee which responsible for evolving JavaScript byES2018 taking proposals and if they’re deemed then they will be added to the next edition).
ES2018
1- Asynchronous iteration :
ES6 had introduced to us an amazing new 6th primitive data type which allows us to iterate via objects anonymously while we can loop through an object via Symbols.
Let’s take a quicker look :
const iterator = {
[Symbol.iterator]: () => {
const data = ['J', 'a', 'v', 'a', 'S', 'c', 'r', 'i' , 'p', 't']
return {
next: () => {
if (data.length) {
return {
value: data.shift(),
done: false
};
} else {
return {
done: true
};
}
}
}
}
}for(const letters of iterator) {
console.log(letters);
}
But what if we have a stream of data which we need to handle asynchronously?!
And here it comes the new Asynchronous iteration feature in ES2018 unlucky the piece of code above which implement the logic of iterating through data using synchronous iteration with Symbols.
Some of you most probably asking why just not return a promise which return an array with the whole data-set?
I could say imagine that we have a large data-set, which will take a little time to load each item, then we need a support for asynchronous sequences of data which they are different from streams… to whom is interested to dive deeper in this talk here’s a short link :
There are some rules to call our object as asynchronously:
1- We need to use Symbol.asyncIterator
instead of Symbol.iterator
2- next()
should return a promise.
3- To iterate over such an object, we should use for...await...of
.
So let’s try it :
const data = ['J', 'a', 'v', 'a', 'S', 'c', 'r', 'i' , 'p', 't'];
// let's suppose we'll get this array asynchronouslyconst iterator = {
[Symbol.asyncIterator]: () => {
return {
next: () => {
if (data.length) {
return Promise.resolve({
value: data.shift(),
done: false
});
} else {
return Promise.resolve({
done: true
});
}
}
}
}
};(async function() {
for await (const item of iterator) {
console.log(item);
}
})();
2- Rest/Spread operators:
In ES2015, rest/spread operators knew a heavy usage among developers instead of using other methods such as concat() and slice() which allows merging and copying arrays in a much simpler way.
But ES2018 came with a very handy tools for object literals and object destructuring.
1- Spread :
const obj = {
a: 1,
b: 2
};const obj1 = {
...obj,
c: 3
};console.log(obj1); // { a: 1, b: 2, c: 3 }
If we would try this on ES2015 then it will throw us an error but with the new ...
in ES2018 for object literal, we can retrieve data from obj and assign them to obj1.
Note that names are matter when copying an object into another, so if there are properties with the same name, then the last one will be used :
const obj = {
a: 1,
b: 2,
c: 3
};const obj1 = {
...obj,
c: 4
};console.log(obj1); // { a: 1, b: 2, c: 4 }
Note also that order is matters when copying objects :
const obj = {
a: 1,
b: 2,
};
const obj1 = {
c: 3,
...obj
};
console.log(obj1); // {c: 3, a: 1, b: 2}
2- Rest:
Array destructuring is a great pattern to extract data from arrays in ES2015, so TC39 made it possible within objects too in ES2018 :
const obj = {
a: 1,
b: 2,
c: 3
};
const { a, ...rest } = obj;console.log(a); // 1
console.log(rest); // { b: 2, c: 3 }
The code above made it possible to copy the enumerable properties into a new object and copy a specific property in demand, but there are some restrictions where rest
properties needs always to be at the end of the object :
const obj = {
a: 1,
b: 2,
c: 3
};
const { ...rest, a } = obj;
console.log(rest); // "SyntaxError: Rest element must be last element
Also we need to remember that we can use only one time a rest operator per level :
const obj = {
a: {
b: 1,
c: 2,
d: 3,
},
e: 4,
f: 5
};const { ...rest, ...rest1} = obj; // "SyntaxError: Rest element must be last elementconst {a: {b, ...rest}, ...rest1} = obj;
console.log(b); // 1
console.log(rest); // { c: 2, d: 3}
console.log(rest1); // { e: 4, f: 5}
3- dotall(.) flag for regular expression :
A very useful update came to empower RegExp. Since before we know that \n
doesn’t match the dot(.) line terminator and astral (non-BMP) characters such as emoji in RegExp pattern :
console.log(/^.$/.test('\n')); // false
console.log(/^.$/.test('😀')); // false
While line terminators recognized by ECMAScript are:
- U+000A LINE FEED (LF) (
\n
) - U+000D CARRIAGE RETURN (CR) (
\r
) - U+2028 LINE SEPARATOR
- U+2029 PARAGRAPH SEPARATOR
So developers couldn’t have a choice to make a match with the dot(.) only through few ways:
console.log(/^[\s\S]$/.test('\n')); // true
console.log(/^[\w\W]$/.test('\n')); // true
console.log(/^[^]$/.test('\n')); // true
Then ECMAScript developers came up with a solution following other programming languages as Perl and PHP by adding the \s
to match a line terminator :
console.log(/newDotAll./s.test('newDotAll\n')); // true
console.log(/^.$/s.test('\n')); // true
console.log(/^.$/s.test('\r')); // true
console.log(/^.$/s.test('\u{2028}')); // true
4- Promise.prototype.finally() :
Imagine that you met a girl and you asked her to go out with you in a date, so you’ll have two results, either you’ll know how to talk with her and she will say yes and your request will be fulfilled or you’ll still need more practicing on how will you do this kind of job and she will reject your request, but in all cases, this step will finally finish with having a new experience either making it or not making it.
That’s how Promise works and that’s how the new finally method works :
let date = new Promise((resolve, reject) => {
let response = Boolean(Math.round(Math.random()));
if(response) {
resolve('she will go out with me');
} else {
reject('she said no');
}
});date.then((success) => {
console.log(success);
}).catch((failure) => {
console.log(failure);
}).finally(()=> {
console.log('I took my chance!');
});
In this example we had a random response and our promise result will depend on it, either it will be true or false, but finally block will act as a cleaner for our pending status and to close the promise.
Hello ES2019
1-flat/flatMap :
Arrays have seen 2 more additional features within ES2019 which made it easy for us to make operation on arrays no matter how deep they are.
flat :
Flat method is basically gonna take an array of arrays and then flattens them into a single array, let’s take a look:
const arr = [1,2,3,4,5, [6,7, [8,9,10]], [11, 12, 13] ];
Before we would apply a normal function in something like this:
const customFlat = (arr) => {
return arr.reduce((acc, cur) => {
return acc.concat(Array.isArray(cur) ? customFlat(cur) : cur);
}, []);
};console.log(customFlat([1,2,3,4,5, [6,7, [8,9,10]], [11, 12, 13] ])); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
So flat comes to the rescue!
const arr = [1,2,3,4,5, [6,7, [8,9,10]], [11, 12, 13] ];const flatArrLev1 = arr.flat(); // [1, 2, 3, 4, 5, 6, 7, [8, 9, 10], 11, 12, 13]
So here we see that flat() takes the first nested level of the array and then takes it at the top level and it takes one argument which is the depth that specifies how many levels down to be flattened. Notice that by default the depth it’s 1.
const flatArrLev2 = arr.flat(2); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
So we notice that [6,7] and [11, 12, 13] was on level 1 so they automatically move to top level, then in our second operation flatArrLev2, we made it on a deeper level on [8, 9, 10] so with that we have our new fresh array without nested values.
PS : if we don’t know how much deep is our nested arrays then we can use Infinity like this :
const flatInfinity = arr.flat(Infinity);
flatMap :
FlatMap is another cool feature which applies two combined operations on an array by first calling map() on each element of the array followed by flat() to flattens the result into the new array :
const arr1 = [ [1, 2], [3, 4], [5, 6] ];console.log(arr1.flatMap(x => x[0]+x[1]));
1- Map on these nested arrays and makes calculation over them: [1,2]…
2-Flatting them
3- Result: [3, 7, 11]
2- Object.fromEntries() :
Object has chipped with lots of methods since the last years, one useful has been introduced within ES2019.
Object.fromEntries is a brand new method which that makes the inverse of Object.entries and it accepts an iterable Map or map-like array and converts a list of key-value pairs into a new object :
So let’s try it ;)
Object.fromEntries(new Map([["a", "b"], ["c", "d"]]));
// Object { a: "b", c: "d" }
Object.fromEntries([["a", "b"], ["c", "d"]]);
// Object { a: "b", c: "d" }
PS: It’s not fully supported by all browsers, so you need to check your engine before using it.
3- trimStart/trimEnd :
Strings has also seen some changes regarding trimming, but some of you may say that we have already trimLeft() and trimRight(), I would say these features only change names and keeps the same effect because it’s more close to human language and we still can use the old trimLeft() and trimRight() as aliases.
let name = " Hamza Amdouni ";
console.log(name.trimStart()); // "Hamza Amdouni "
console.log(name.trimEnd()); // " Hamza Amdouni"console.log(name.trimStart() === name.trimLeft()); // true
4- Symbol.prototype.description :
Describing a Symbol was a bit bothersome while we had only one way which is to convert it into a string :
const symbol = Symbol('new Symbol');
console.log(String(symbol) === 'Symbol(new Symbol)'); // true
Thanks to Michael Ficarra with his new proposal, he gave us a straightforward way to access to symbol descriptor :
const symbol = Symbol('new Symbol');
console.log(symbol.description === 'new Symbol'); // true
Conclusion
After all I just wanted to share what I got learned from ES2018/ES2019 and any suggestions, comments are accepted while looking forward for your claps ;)