7 min read

cameron-venti-3rabTGLccwc-unsplash

What's new in ES2018 (ES9)


ES2018 (ES9)

ECMAScript 2018 was released in June 2018, adds fewer features than major editions (ES2016, ES2017) used to. These new changes are Asynchronous Iteration, Rest/Spread Properties, new features to Regular Expressions and a revision to template literals.

Asynchronous Iteration

With synchronous iterations, we are able to iterate data from a synchronous source, but not when data is from an asynchronous source, for example https fetch.

We will recap synchronous iteration and then asynchronous iteration.

Synchronous iteration

Synchronous iteration was introduced with ES6 and have the following components

  • Iterable: Is an object that can be iterated via a method with key Symbol.iterator
  • Iterator: An object returned by [Symbol.iterator](). It has a method .next() that returns the next element.
  • NextElement: Is the object returned by .next() that has 2 properties:
    • value: Contains the value to be used.
    • done: Is a boolean that becomes true after the last element.

If you need some extra info about Iterator pattern. Check this link

Asynchronous Iteration

The previously explained way of iterating is synchronous, it doesn’t work for asynchronous sources. For example, in the following code, fetchCoinValues() can't deliver its asynchronous data via synchronous iteration:

for (const coinValue of fetchCoinValues(['ETH','XMR','BTC','LTC','BAT'])) {
  console.log(coinValue);
}

ES2018 specifies a new protocol for async iteration:

  • Async iterables are marked via Symbol.asyncIterator.
  • Method .next() of an async iterator returns a Promise or NextElement.

With this new protocol, we are able to await for an asynchronous fetched coinValue

for await (const coinValue of fetchCoinValues(['ETH', 'XMR', 'BTC', 'LTC', 'BAT'])) {
  console.log(coinValue);
}

Rest/Spread Properties

We have a new (or two) operator(s).

  • The rest operator(...) is used in object destructuring.
  • The spread operator(...) in object literals.

(Yes, looks similar =))

Rest operator

Inside object destructuring patterns, the rest operator (...) copies all enumerable own properties of the destructuring source (foo) into its operand( const { }), except those that were already mentioned in the object literal.

const foo = {
  empanadas: 6,
  milanesas: 2,
  yerba: '1 Kg'
};

const { yerba, ...rest } = foo;

console.log(yerba); // Prints "1 Kg"
console.log(rest); // Prints {empanadas: 6, milanesas: 2}

We can do it inside of a function, to handle named parameters

function func({ yerba, ...rest }) {
  // rest operator
  /**
   * You can eat empanadas and drink mate here using property rest.empanadas
   * and rest.yerba.
   */
}

We can use the rest operator at most once and it must be last.

const  { ...rest, yerba } = foo; // syntax error, must be last.
const  { ...rest, ...others } = foo; // syntax error, 2 operators.

Spread operator

In right side object literals ({...foo, water: "1 L"}), the spread operator (...) inserts all enumerable own properties of its operand (foo) into the object created (bar) via the literal:

const foo = {
  empanadas: 6,
  milanesas: 2,
  yerba: '1 Kg'
};

const bar = { ...foo, water: '1 L' };

console.log(bar); // Prints
/**
 * {
 *   empanadas: 6,
 *   milanesas: 2,
 *   yerba: "1 Kg",
 *   water: "1 L",
 **/

If we have conflicts with keys (empanadas), last one wins (empanadas : 12). This is useful to override default keys.

const foo = {
  empanadas: 6,
  milanesas: 2,
  yerba: '1 Kg'
};

const bar = { ...foo, empanadas: 12 };

console.log(bar); // Prints
/**
 * {
 *   empanadas: 12,
 *   milanesas: 2,
 *   yerba: "1 Kg",
 **/

New features related to regular expression

This release brings 4 new features to regulars expressions

Flag /s (dotAll)

Dot (.) in regular expressions don't match line terminators

console.log(/^.$/.test('\n')); // Prints false

DotAll flag (/s) allows . in a regExp to match line terminators ( /n) and emojis (😁)

console.log(/^.$/s.test('😁')); // Prints true

Unicode Property Escapes

Unicode standard assigns properties to each symbol, like White_Space. Now, we can access this properties inside of RegEXp using flag /u.

const result = /^\p{White_Space}+$/u.test('3 empanadas');

console.log(result); // Prints true

This new feature makes RegExp much more readable.

Lookbehind Assertions

A lookbehind assertion is denoted by ?<=, and enables you to match a pattern based on the substring that precedes the pattern. For example,

const regExp = /(?<=\$|£|€)\d+(\.\d*)?/;

console.log(regExp.exec('199')); // Prints null
console.log(regExp.exec('$199')[0]); // Prints 199

Also, we have the negative version replacing = with !, that is ?<!

const negativeRegExp = /(?<!\$|£|€)\d+(\.\d*)?/;

Remember that the substring matched by the lookbehind expression isn't captured and isn't part of the result.

Named capture groups

Named capture groups enable you to take apart a string with a regular expression.

Matching a regular expression returns an object. If a fragment of the regular expression is inside a parentheses, is a capture group and it's stored in the matched object.

Prior to ES2018, all capture groups were indexed by indexes

const eventDate = /([0-9]{4})-([0-9]{2})-([0-9]{2})/;

const matchedObject = eventDate.exec('2019-04-03');
console.log(matchedObject[1]); // 2019
console.log(matchedObject[2]); // 04
console.log(matchedObject[3]); // 23

After this update, we can name groups year, month and day using <groupName>, that are stored inside groups key:

const eventDate = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/;

const matchedObject = eventDate.exec('2019-04-03');
console.log(matchedObject.groups.year); // Prints 2019
console.log(matchedObject.groups.month); // Prints 04
console.log(matchedObject.groups.day); // Prints 03

We can combine with destructuring!

const {
  groups: { day, month }
} = matchedObject.exec('2019-04-03');

console.log(day); // Prints 03

With this update is easier to find groups by ID(year, month, day), each group is self descriptive.

Promise .finally

ES2018 introduces a new callback that is always executed, no matter if then or catch is called.

fetch(url)
  .then()
  .catch()
  .finally(() => console.log(`I'm always called!`));

.finally is useful when you need to do some clean up after the operation has finished regardless of whether or not it succeeded,for example to close a connection.

Template literal revision

When a template literal (${food} is tasty) is immediately preceded by an expression (fn'${food} is tasty'), it is called a tagged template literal. A tagged template comes in handy when you want to parse a template literal with a function(fn). Consider:

function fn(string, substitute) {
  if (substitute === 'empanadas') {
    substitute = 'milanesas with mashed potatoes';
  }
  return substitute + string[1];
}

const food = 'empanadas';
const result = fn`${food} are tasty!`;

console.log(result); // Prints milanesas with mashed potatoes are tasty!

It has problems with escape sequences like hex (\x), unicode (\u), or octal (\ followed by a digit). Strings like C:\xxx or \ubuntu are considered invalid escape sequences and throws a SyntaxError.

With ES2018 revision, each illegal sequence is undefined

function fn(string, substitute) {
  console.log(substitute); // Prints escape sequences:
  console.log(string[1]); // Prints undefined
}

const str = 'escape sequences:';
const result = fn`${str} \ubuntu C:\xxx\uuu`;

Keep in mind that using illegal escape sequences in a regular template literal still causes an error:

const result = `\ubuntu`; // SyntaxError: Invalid Unicode escape sequence

Conclusions

This is a lot to digest, but each word, revision, feature and release will improve or programming skills. Remember that, having this in mind,and using it, our code will be shorter and cleaner than before.

Sources