topic: JavaScript by Milos Protic relates to: Web Development, ES10, ES2019, Features, Proposal on October, 01 2019
8 New ES10 (ES2019) Features by Example
Introduction
Hated by many, and also loved by many, JavaScript is one of the de-facto web development standards. Up to recently, it had a monopoly when it comes to developing web-based apps (this is no longer the case because of the WebAssembly or WASM), and it has gone a long way since its beginnings. In this article, I will cover 8 new features introduced as a part of ES10 or if you prefer, ES2019.
This is a list of (8) new ES features we will be covering here:
- Optional Catch Binding
- Object.fromEntries
- Array.flat
- Array.flatMap
- String#{trimStart,trimEnd}
- Dynamic Import (at the time of writing this article, the proposal was in stage 3 - see here)
- globalThis object (at the time of writing this article, the proposal was in stage 4 - see here)
Do note that there are a couple more of them introduced in ES10. This article covers only the ones I find most usable in practice. Feel free to comment if you have anything to add
1. Optional Catch Binding
Until this was possible, we as developers were forced to bind an exception whenever we used try...catch
statement. The engine didn't care if the exception was later used or not, which was a bad thing.
Bad:
try {
// some code
return true;
} catch(unusedException) { // here is the problem
return false;
}
Good (the new ES10 feature):
try {
// some code
return true;
} catch {
return false;
}
As you can see, we are now able to omit the exception binding if it's not required. If you ask me, it's an improvement. This way our code is cleaner and less confusing for the next guy (a person that will continue our work at some point in the future).
2. Object.fromEntries()
It might not be that clear from the method name, but the Object.fromEntries
method transforms a list of key-value pairs into an object. You can transform a map or array of arrays into an object. It does the reverse of Object.entries
, which converts the given object into its key-value pair representation.
Imagine that you have a list of users containing the user name and age as individual items. You can use Object.fromEntries
to transform that list into an object.
const users = [['John', 49], ['Frank', 25], ['David', 36]];
const usersAge = Object.fromEntries(users);
console.log(usersAge);
// outputs: {John: 49, Frank: 25, David: 36}
3. Array.flat()
This method creates a new array with all sub-array elements concatenated into it recursively up to the specified depth, meaning that we can get a single array as a result in the case when we have an array of arrays. Not every item of the source array needs to be an array. Array.flat
is smart enough to figure that out. The default depth is 1.
We can try to illustrate our example by thinking of the link between postal codes and cities. Sometimes, the same postal code can reference two cities if they are located in a different state (if I'm not mistaken :)). And your application might require a list of all cities regardless of its postal code, therefore it might end up with a list looking something like this:
['City 1', ['City 2'], ['City 3', 'City 4']]
To make this list easier to use and iterate through, we could flatten it by using the Array.flat
method.
const cities = ['City 1', ['City 2'], ['City 3', 'City 4']];
console.log(cities.flat()); // outputs ["City 1", "City 2", "City 3", "City 4"]
Also, I find it useful to mention that Array.flat
will remove all empty slots from our array.
const numbers = [1, 2, [3, 4],, 5, 6,, [7,8]];
console.log(numbers.flat()); // outputs [1, 2, 3, 4, 5, 6, 7, 8]
4. Array.flatMap()
This method first maps each element using a mapping function, then flattens the result into a new array.
For this example, we could modify the users
list from the Object.fromEntries
example above.
Imagine that, besides name and age, we receive the user followers within each item in the list. Object.fromEntries
cannot help us in this case because it will simply ignore the third element considering that it only works with key-value pairs meaning that the maximum number of items for each element is 2.
Instead, we should use Array.flatMap
to achieve the same-ish result. In my opinion, this result is a little better because it will provide more context.
const users = [['John', 49, 96], ['Frank', 25, 388], ['David', 36, 14]];
const usersFlattened = users.flatMap(([name, age, followers]) => {
return { name, age, followers };
});
console.log(usersFlattened);
// outputs:
// 0: {name: "John", age: 49, followers: 96}
// 1: {name: "Frank", age: 25, followers: 388}
// 2: {name: "David", age: 36, followers: 14}
// length: 3
5-6. String.trimStart() & String.trimEnd()
String.trimStart
method removes whitespace from the beginning of a string, and String.trimEnd
method removes whitespace from the end of a string. Both of them have an alias, trimLeft
and trimRight
correspondingly.
const message = ' Hello ES10 ';
console.log(message.trimStart()); // outputs 'Hello ES10 '
console.log(message.trimEnd()); // outputs ' Hello ES10'
console.log(message); // outputs ' Hello ES10 '
Note that both of these methods return a new string with whitespaces stripped. The original string is not modified.
7. Dynamic Import
By dynamically importing a module we are receiving a promise for the module namespace object (exports) of the requested module. We can go one step further and also use async/await to assign the import to a variable.
Our module could look something like this:
// Default export
export default () => {
console.log('Do default');
};
// Named export
export const doSomething = () => {
console.log('Do something');
};
We are now able to import it dynamically in one of the following ways:
import('..my-path/my-module.js').then((module) => {
module.default();
module.doSomething();
// ...
});
or
(async () => {
const module = await import('..my-path/my-module.js')
module.default();
module.doSomething();
// ...
})();
8. globalThis Object
Prior to the globalThis
standardization, we had a couple of ways to determine the global object of the environment our application resides in. The first approach was to use the Function
like this:
const global = Function('return this')();
For example, in browser, this will return the Window object. However, the given approach causes CSP violations which denies the Function
usage in such a manner and due to that we had to use a manual check to determine the global object, like this:
const getGlobal = function () {
if (typeof self !== 'undefined') { return self; }
if (typeof window !== 'undefined') { return window; }
if (typeof global !== 'undefined') { return global; }
throw new Error('unable to locate global object');
}
const global = getGlobal(); // will return window object in the browser
// array usage example
const numbers = new global.Array(1, 2, 3);
console.log(numbers); // outputs [1, 2, 3];
Usually, library builders use this approach and wrap their code with the immediate function providing the correct global object for the library. It looks something like this:
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = global || self, global.myLib = factory());
}(this, function () {
// the code
return {} // return the lib namespace
}));
In the modern era, this is done automatically by the bundlers like rollup.js or webpack
With the globalThis
proposal, this will no longer be needed and the global object will be standardized for us.
Conclusion
JavaScript is getting a lot of improvements, for sure. Some of them are more useful than others, but overall they are making our lives easier enabling us to write clean and maintainable code. I'm excited to see what does the future holds!
If you liked the article, consider buying me a coffee and subscribe here or follow me on twitter to stay tuned.
And as always, thanks for reading and see you in the next article.
Subscribe to get the latest posts delivered right to your inbox