ES2019 was recently finalized, which means JavaScript has several new language features to learn. These features are now available in Chrome 73:
As of @v8js v7.3 / Chrome 73, all of these ES2019 features are available by default. Enjoy! https://t.co/vtvx8VTRYw
— V8 (@v8js) January 29, 2019
Unfortunately there's no version of Node.js at the time of this writing that uses V8 7.3, the current Node.js 12 nightly build still uses V8 7.1. However, V8 v7.0 has most of the ES2019 feature set, see the list below. For the purposes of this article we'll use Node.js 11.6 (V8 v7.0.276).
Node.js 11.6 ES2019 Support |
---|
✓ Array#flatMap() , Array#flat() |
✗ Object.fromEntries() |
✓ String#trimStart() , String#trimEnd() |
✓ Symbol#description |
✓ Optional catch binding |
✓ JSON superset |
✗ Well-formed JSON.stringify() |
✓ Stable Array#sort() |
✓ Function#toString() Revision |
Array#flat()
and Array#flatMap()
The functions responsible for the infamous smoosh-gate controversy have finally landed in ES2019. Thankfully, we don't have to write smooshMap()
or componentDidSmoosh()
. The flat()
and flatMap()
functions are available in Chrome 69 / V8 6.9, so you need Node.js 11.
Array#flat()
is analogous to Lodash's _.flattenDepth()
function. The flat()
function takes an array of arrays and "flattens" the nested arrays into the top-level array.
[[1, 2], [3, 4], [5, 6]].flat(); // [1, 2, 3, 4, 5, 6]
By default, flat()
only flattens one level deep:
[[[1, 2]], [[3, 4]], [[5, 6]]].flat(); // [ [ 1, 2 ], [ 3, 4 ], [ 5, 6 ] ]
However, flat()
takes a depth
argument that specifies how many levels down you want to flatten.
[[[1, 2]], [[3, 4]], [[5, 6]]].flat(2); // [1, 2, 3, 4, 5, 6]
The new flatMap()
function is equivalent to calling map()
followed by flat()
. This is handy if your map()
returns an array.
const oddNumbers = [1, 3, 5, 7, 9];
// [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
const allNumbers = oddNumbers.flatMap(num => ([num, num + 1]));
Since the callback
parameter to flatMap()
above returns an array, flatMap()
flattens out the array. The above is equivalent to:
const allNumbers = oddNumbers.map(num => ([num, num + 1])).flat();
One neat trick with flatMap()
is that you can do both filter()
and map()
in one step. You can filter out an element by returning an empty array []
from your flatMap()
callback.
// [ 1, 3, 5, 7, 9 ]
const oddNumbers = allNumbers.flatMap(num => num % 2 === 0 ? [] : num);
Object.fromEntries()
The Object.fromEntries() function is currently not available in any version of Node.js. You need to npm install
the object.fromentries
polyfill.
The Object.fromEntries()
function is intended to make it easy to convert a JavaScript Map into a JavaScript object:
// // { hello: 'world', foo: 'bar' }
Object.fromEntries(new Map([['hello', 'world'], ['foo', 'bar']]));
A neat side effect is you can convert an array of key/value pairs into a JavaScript object:
// { hello: 'world', foo: 'bar' }
Object.fromEntries([['hello', 'world'], ['foo', 'bar']]);
The primary rationale for this function is converting a map into an object, but the key/value pairs conversion is also useful.
For example, MongoDB doesn't allow storing keys with .
. The way to store potentially dotted key names in MongoDB is with a key/value array. The Object.fromEntries()
function along with its inverse Object.entries()
make it easy to switch between objects and key/value arrays.
const packages = { 'lodash.get': '4.x', 'object.fromentries': '2.x' };
// Stores `[ [ 'lodash.get', '4.x' ], [ 'object.fromentries', '2.x' ] ]`
await mongoose.model('Project').create({ packages: Object.entries(packages) });
await mongoose.model('Project').findOne().then(doc => {
// Convert `[ [ 'lodash.get', '4.x' ], [ 'object.fromentries', '2.x' ] ]`
// back into a native JavaScript object.
return Object.assign(doc, { packages: Object.fromEntries(doc.packages) });
});
Symbol#description
Symbols were introduced in ES2015 as a way of avoiding naming conflicts between properties. A symbol is a guaranteed unique property name:
const sym1 = Symbol('foo');
const sym2 = Symbol('foo');
const obj = { [sym1]: 'bar', [sym2]: 'baz' };
console.log(obj[sym1]); // 'bar'
console.log(obj[sym2]); // 'baz'
The first parameter to Symbol()
is called the symbol's description. The
description isn't an id, any number of symbols can have the same description
without conflicts. A symbol's description is purely for debugging. The only
exception is if you register the symbol in the global symbol registry using
Symbol.for()
.
Before ES2019, the only way you could access a symbol's description was using .toString()
:
const sym = Symbol('foo');
sym.toString(); // Symbol('foo')
In Node.js 11 you can also access sym.description
:
sym.description; // foo
The description
property is not writable. Setting sym.description
is a no-op.
sym.description; // foo
sym.description = 'bar';
sym.description; // still "foo"
Optional catch
Binding
In Node.js 8 you always need to specify a variable in the catch
clause of a try/catch
, even if you never use the variable:
try {
// ...
} catch (err) {
// Need to specify `err`, even if it isn't used.
}
ES2019's optional catch
binding lets you skip defining a variable in catch
. Optional catch
binding is available in Node.js 10 and Chrome 66.
try {
// ...
} catch {
// No `err` variable
}
Moving On
ES2019's feature set isn't nearly as exciting as ES2015 or async/await in ES2017, but these new features help round out the language's API. Array#flat()
and Array#flatMap()
extend JavaScript's existing support for chainable array manipulation. Object.fromEntries()
is a neat complement to Object.entries()
. Symbol#description
addresses a minor but cumbersome gap in ES6 symbols. Overall, ES2019 is a small but useful step in the right direction for JavaScript.