ES2019 was recently finalized, which means JavaScript has several new language features to learn. These features are now available in Chrome 73:

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.

Found a typo or error? Open up a pull request! This post is available as markdown on Github
comments powered by Disqus