JavaScript: useful features of ECMAScript 2019 reviewed

First of all let me apologise for not writing as frequent as I would’ve liked. It’s been a crazy busy couple of weeks and I had heaps of fun speaking at DDD Perth 2019.

That taken care of, this time I thought let’s go through the new features which are added into the ECMAScript 2019 (aka ES2019 or ES10), because they’re super exciting and make our lives much easier 😎.

TLDR;

At a glance, this version adds a few built-in functions on Array.prototype, flat and flatMap. Then we have Object.fromEntries for directly turning the return value of Object.entries into a new Object.

We also have trimStart and trimEnd on String.prototype over the widely used non-standard versions String.prototype.trimLeft and trimRight.

Another exciting feature is optional catch binding parameters. In addition to those, there are some JSON improvements, addition of Symbol.prototype.description which allows you to specify a description for your symbol, JSON.stringify returns well-formed UTF-8 regardless of input, and at last clarifying Function.prototype.toString by requiring that it either return the corresponding original source text or a standard placeholder.

So if you’re ready let’s dig in.

Array.prototype.{flat, flatMap}

flat() is a new method which let’s you create a one-dimensional array from a multi-dimensional one.

Imagine we have an array like below:

const myArray = [1, 2, 3, [4, 5, 6, [7, 8, 9, [10, 11, 12]]]];

Prior to flat if you wanted to achieve this, you had to do something like this:

const myNewArray = [].concat.apply([], myArray)
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Or:

var myNewArray = myArray.reduce(
  function(prev,curr) {
    return prev.concat(curr)
  }
)
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

With this new addition, it would be as simple as:

var myNewArray = myArray.flat(4);
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

You can also chain multiple calls:

var myNewArray = myArray.flat().flat().flat().flat();
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

The argument into the flat function just specifies how deep it should look into the array. If you’re not sure how deep the array is, simply use Infinity as input:

var myNewArray = myArray.flat(Infinity);
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

And if you don’t provide any input, by default it only goes one level deep:

var myNewArray = myArray.flat(); 
// [1, 2, 3, 4, 5, 6, Array(4)];

flatMap on the other hand, allows you to map each element using a mapping function and then flattens the result into a new array. Think of it as chaining a map function with a single flat. However, it can be really helpful both in terms of usage and efficiency of execution.

let myArray = [1, 2, 3, 4, 5];

let mappedArray = myArray.map(x => [x, x * 2]);
// [Array(2), Array(2), Array(2), Array(2), Array(2)]
// 0: (2)[1, 2]
// 1: (2)[2, 4]
// 2: (2)[3, 6]
// 3: (2)[4, 8]
// 4: (2)[5, 10]

let mappedFlattenedArr = mappedArray.flat();

// [1, 2, 2, 4, 3, 6, 4, 8, 5, 10]

let myNewArray = myArray.flatMap(v => [v, v * 2]);
// [1, 2, 2, 4, 3, 6, 4, 8, 5, 10]

String.prototype.{trimStart, .trimEnd}

These methods are fairly obvious as to what they will do for us. Just keep in mind that we had trimLeft which will be replaced by trimStart and trimRight which will be replaced by trimEnd.

let message = ' This is a string with white space around ';

message = message.trimStart();
// 'This is a string with white space around '

message = message.trimEnd();
// 'This is a string with white space around'

Object.fromEntries

This method get an Iterable and transforms key-value pairs to an object. But let’s see what is an Iterable:

JavaScript supports a protocol by which objects such as arrays can be used by control structures such as forof and the spread operator ... to loop through data sequentially. This is referred to as the iterable and the data structures that support this functionality are called iterables. While JavaScript provides maps, arrays and sets with an iterable property from the get-go, plain objects do not have this by default.

To see this in action:

let entries = new Map([["name", "john"], ["age", 22]]);

let newObj = Object.fromEntries(entries);
// { name: 'john', age: 22 }

A real life use case is when parsing query strings:

let query = Object.fromEntries(new URLSearchParams('foo=bar&baz=qux'));

// { foo: 'bar', baz: 'qux' }

Optional Catch Binding

Optional catch binding allows us to use try/catch without the error parameter inside the catch block.

Previously you had to use this syntax regardless of whether you cared about err or not, such as when you had to fall back to feature to support older browsers:

try {
  // try to use a web feature which may not be implemented
} catch (unused) {
  // fall back to a less desirable web feature with broader support
}

With ES2019 you can do:

try {
  // ...
} catch {
  // ...
}

Symbol.description

This new read-only description property is a string returning the optional description of Symbol objects. This method is encouraged to be used instead of Symbol.prototype.toString where it wasn’t obvious what will be returned.

let description = 'This symbol represents an emoji 😁';

let mySym = Symbol(description);
// Symbol('This symbol represents an emoji 😁')

console.log(mySym.description);
'This symbol represents an emoji 😁'

Function.toString

This method is a really useful one which returns the source code of a function. Imagine doing a troubleshooting on a block of code which is using a third party function. This can potentially help you to see the implementation (given it’s got source maps).

function myFn(emoji) {
  let msg = `${emoji} is hellav an emoji`;
  console.log(msg);
}

console.log(myFn.toString());

// "function myFn(emoji) {
//   let msg = `${emoji} is hellav an emoji`;
//   console.log(msg);
// }"

Of course this doesn’t work for everything. If we try it for map function on array:

Array.prototype.map.toString();

// "function map() { [native code] }"

Since the implementation is not written in JavaScript, it will just show you that this function is written in native code.

JSON.stringify

The team have done an improvement with Unicode characters and now this method can’t return malformed data.

// Non-BMP characters still serialize to surrogate pairs.
JSON.stringify('𝌆')
// → '"𝌆"'
JSON.stringify('\uD834\uDF06')
// → '"𝌆"'

// Unpaired surrogate code units will serialize to escape sequences.
JSON.stringify('\uDF06\uD834')
// → '"\\udf06\\ud834"'
JSON.stringify('\uDEAD')
// → '"\\udead"'

Array.sort Stability

The team has decided to change the sort implementation so that it would be stable (that is, elements that compare equal must remain in their original order).

const grades = [
  { topic: 'math', grade: 10 },
  { topic: 'literacy', grade: 10 },
  { topic: 'chemical', grade: 13 },
  { topic: 'geography', grade: 12 },
  { topic: 'modern history', grade: 12 },
  { topic: 'art', grade: 13 },
  { topic: 'computer basics', grade: 14 },
  { topic: 'algebra', grade: 14 },
];

grades.sort(a, b => a.grade - b.grade);

// 0: {topic: "math", grade: 10}
// 1: {topic: "literacy", grade: 10}
// 2: {topic: "geography", grade: 12}
// 3: {topic: "modern history", grade: 12}
// 4: {topic: "chemical", grade: 13}
// 5: {topic: "art", grade: 13}
// 6: {topic: "computer basics", grade: 14}
// 7: {topic: "algebra", grade: 14}

Summary

In general JavaScript is moving in the right direction for helping developers write more stable, reliable and consistent code. You can find more information on their GitHub repo here.

Hope to see more awesome features in JavaScript and see you soon with another article.