Skip to content

ECMAScript 2018 (ES9)

Asynchronous Iteration

Synchronous iteration was introduced with ES6, now comes the counterpart.

With Asynchronous Iteration we get asynchronous iterators and asynchronous iterables. Asynchronous iterators just like regular iterators, except their next() method returns a promise for a { value, done } pair. To consume asynchronous iterables, we can now use the await keyword with for … of loops.

for await (const line of readLines(file)) {
  console.log(line)
}

Rest/Spread Properties

When destructuring an object, Object Rest Properties allow you to collect the remaining properties of an object onto a new object. Think of it as a magic magnet attracting all leftovers.

const data = { a: 1, b: 2, c: 3, d: 4 }
const { c, a, ...x}
console.log(a) // 1
console.log(c) // 3
console.log(x) // { b: 2, d: 4 }

RegExp named capture groups

Such expressions may look like:

(?<year>[0-9]{4})

Here we have tagged the previous capture group #1 with the name year. After matching, you can access the captured string via matchObj.groups.year.

Let’s rewrite the previous code so that it uses named capture groups:

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

const matchObj = RE_DATE.exec('1999-12-31');
const year = matchObj.groups.year; // 1999
const month = matchObj.groups.month; // 12
const day = matchObj.groups.day; // 31

Named capture groups also create indexed entries; as if they were numbered capture groups:

const year2 = matchObj[1]; // 1999
const month2 = matchObj[2]; // 12
const day2 = matchObj[3]; // 31

Destructuring can help with getting data out of the match object:

const {
    groups: { day, year }
} = RE_DATE.exec('1999-12-31');
console.log(year); // 1999
console.log(day); // 31

You get the following benefits:

  • It’s easier to find the “ID” of a capture group.
  • The matching code becomes self-descriptive, as the ID of a capture group describes what is being captured.
  • You don’t have to change the matching code if you change the order of the capture groups.
  • The names of the capture groups also make the regular expression easier to understand, as you can see directly what each group is for.
  • You can freely mix numbered and named capture groups.

Back references

\k<name> in a regular expression means: match the string that was previously matched by the named capture group name. For example:

const RE_TWICE = /^(?<word>[a-z]+)!\k<word>$/;
RE_TWICE.test('abc!abc'); // true
RE_TWICE.test('abc!ab'); // false

The back reference syntax for numbered capture groups works for named capture groups, too. And you can freely mix both syntaxes.

replace() and named capture groups

The string method replace() supports named capture groups in two ways.

First, you can mention their names in the replacement string:

const RE_DATE = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/;
console.log('1999-12-31'.replace(RE_DATE, '$<month>/$<day>/$<year>'));
// 12/31/1999

Second, each replacement function receives an additional parameter that holds an object with data captured via named groups. For example (line A):

const RE_DATE = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/;
console.log(
    '1999-12-31'.replace(
        RE_DATE,
        (
            all,
            y,
            m,
            d,
            offset,
            input,
            { year, month, day } // (A)
        ) => month + '/' + day + '/' + year
    )
);
// 12/31/1999

The last parameter is new and contains one property for each of the three named capture groups year, month and day. We use destructuring to access those properties.

console.log(
    '1999-12-31'.replace(RE_DATE, (...args) => {
        const { year, month, day } = args[args.length - 1];
        return month + '/' + day + '/' + year;
    })
);
// 12/31/1999

RegExp Unicode Property Escapes

Unicode characters can be matched by mentioning their Unicode character properties inside the curly braces of \p{} like:

/^\p{White_Space}+$/u.test('\t \n\r')  // => true
/^\p{Script=Greek}+$/u.test('μετά')    // => true

As you can see, one of the benefits of property escapes is is that they make regular expressions more self-descriptive.

Like in other regular expression escapes you can negate it by using uppercase. So while \p matches character like defined \P matches only the ones not defined.

Examples of properties

  • Name: a unique name, composed of uppercase letters, digits, hyphens and spaces.
    • A: Name=LATIN CAPITAL LETTER A
    • 😀: Name=GRINNING FACE
  • General_Category: categorizes characters.
    • x: General_Category=Lowercase_Letter
    • $: General_Category=Currency_Symbol
  • White_Space: used for marking invisible spacing characters, such as spaces, tabs and newlines.
    • \t: White_Space
    • π: White_Space=False
  • Age: version of the Unicode Standard in which a character was introduced. The euro sign was added in version 2.1 of the Unicode standard.
    • : Age=2.1
  • Block: a contiguous range of code points. Blocks don’t overlap and their names are unique.
    • S: Block=Basic_Latin
    • Д: Block=Cyrillic
  • Script: is a collection of characters used by one or more writing systems.
    • α: Script=Greek
    • א: Script=Hebrew

RegExp Look-behind Assertions

Look-behind assertions work like lookahead assertions, but in the opposite direction.

Positive look-behind assertions

For a positive look-behind assertion, the text preceding the current location must match the assertion (but nothing else happens).

const RE_DOLLAR_PREFIX = /(?<=\$)foo/g;
'$foo %foo foo'.replace(RE_DOLLAR_PREFIX, 'bar');
// '$bar %foo foo'

As you can see, foo is only replaced if it is preceded by a dollar sign. You can also see that the dollar sign is not part of the total match, because the latter is completely replaced by 'bar'.

Negative look-behind assertions

A negative look-behind assertion only matches if the current location is not preceded by the assertion, but has no other effect.

const RE_NO_DOLLAR_PREFIX = /(?<!\$)foo/g;
'$foo %foo foo'.replace(RE_NO_DOLLAR_PREFIX, 'bar');
// '$foo %bar bar'

s (dotAll) flag for regular expressions

The proposal introduces the regular expression flag /s (short for “single line”), which leads to the dot matching line terminators:

/^.$/s.test('\n') // => true

Promise.prototype.finally()

Promise.prototype.finally() finalizes the whole promises implementation, allowing you to register a callback to be invoked when a promise is settled (either fulfilled, or rejected).

A typical use case is to hide a spinner after a fetch() request: instead of duplicating the logic inside the last .then() and .catch(), one can now place it inside .finally()

Template Literal Revision

The problem is that even with the raw version, you don’t have total freedom within template literals in ES2016. After a backslash, some sequences of characters are not legal anymore:

  • \u starts a Unicode escape, which must look like \u{1F4A4} or \u004B.
  • \x starts a hex escape, which must look like \x4B.
  • \ plus digit starts an octal escape (such as \141). Octal escapes are forbidden in template literals and strict mode string literals.

That prevents tagged template literals such as:

latex`\unicode`
windowsPath`C:\uuu\xxx\111`

The solution is drop all syntactic restrictions related to escape sequences. Then illegal escape sequences simply show up verbatim in the raw representation. But what about the cooked representation? Every template string with an illegal escape sequence is an undefined element in the cooked Array: