NexusCS

ES2018

JavaScript
ES2018 (ES9) features: async iteration, object rest/spread, Promise.finally(), RegExp improvements
es2018
es9
javascript
ecmascript

Getting started

Async Iteration

async function* asyncGenerator() {
  let i = 0;
  while (i < 3) yield i++;
}

(async () => {
  for await (const num of asyncGenerator()) {
    console.log(num); // 0, 1, 2
  }
})();

Iterate over async data sources.

Object Rest/Spread

// Rest properties
const { a, ...rest } = { a: 1, b: 2, c: 3 };
// a = 1, rest = { b: 2, c: 3 }

// Spread properties
const cloned = { ...obj };
const merged = { ...obj1, ...obj2 };

Extract and merge object properties.

Promise.finally()

fetch(url)
  .then((res) => res.json())
  .catch((err) => console.error(err))
  .finally(() => {
    isLoading = false;
  });

Cleanup logic for promises.

Async Iteration

for await...of

async function* asyncGenerator() {
  yield Promise.resolve(1);
  yield Promise.resolve(2);
  yield Promise.resolve(3);
}

for await (const num of asyncGenerator()) {
  console.log(num); // 1, 2, 3
}

Iterate over async iterables.

Streaming Data

async function* streamAsyncIterable(stream) {
  const reader = stream.getReader();
  try {
    while (true) {
      const { done, value } = await reader.read();
      if (done) return;
      yield value;
    }
  } finally {
    reader.releaseLock();
  }
}

for await (const chunk of streamAsyncIterable(stream)) {
  console.log(chunk);
}

Process streams asynchronously.

Async Iterable Protocol

const asyncIterable = {
  [Symbol.asyncIterator]() {
    let i = 0;
    return {
      async next() {
        if (i < 3) {
          return { value: i++, done: false };
        }
        return { done: true };
      },
    };
  },
};

for await (const num of asyncIterable) {
  console.log(num); // 0, 1, 2
}

Implement custom async iterables.

Object Rest/Spread

Object Rest

const { a, b, ...rest } = { a: 1, b: 2, c: 3, d: 4 };
// a = 1, b = 2, rest = { c: 3, d: 4 }

// Extract known properties
const { x, y, ...coords } = point;

Collect remaining properties.

Object Spread

// Shallow copy
const cloned = { ...obj };

// Merge objects
const merged = { ...obj1, ...obj2 };

// Override defaults
const config = { ...defaults, ...options };

Replaces Object.assign({}, obj).

Conditional Properties

const config = {
  mode: "production",
  ...(isSummer && { theme: "light" }),
  ...(debug && { logging: true }),
};

// Add property conditionally
const user = {
  name: "John",
  ...(age && { age }),
};

Conditionally include properties.

Nested Objects

// Shallow copy (nested objects shared)
const copy = { ...original };
copy.nested.value = 42; // Mutates original!

// Deep copy alternative
const deepCopy = JSON.parse(JSON.stringify(obj));

⚠️ Creates shallow copies only.

Promise.finally()

Basic Usage

fetch(url)
  .then((res) => res.json())
  .then((data) => console.log(data))
  .catch((err) => console.error(err))
  .finally(() => {
    isLoading = false;
  });

Run cleanup after promise settles.

Pass-through Behavior

Promise.resolve(2)
  .finally(() => 77)
  .then((v) => console.log(v)); // 2

Promise.reject(3)
  .finally(() => 88)
  .catch((e) => console.log(e)); // 3

Returns original promise value.

Exception Override

Promise.resolve(2)
  .finally(() => {
    throw 99;
  })
  .catch((e) => console.log(e)); // 99

Promise.reject(3)
  .finally(() => Promise.reject(100))
  .catch((e) => console.log(e)); // 100

⚠️ Throwing overrides original value.

Async Cleanup

fetch(url)
  .finally(async () => {
    await closeConnection();
    await logActivity();
  })
  .then((data) => console.log(data));

Supports async cleanup logic.

RegExp Named Capture Groups

Basic Syntax

const re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = "2023-12-25".match(re);

match.groups.year; // "2023"
match.groups.month; // "12"
match.groups.day; // "25"

Name capturing groups.

Named Backreference

// Match opening and closing quotes
/(?<quote>['"]).*?\k<quote>/.test(`"hello"`); // true
/(?<quote>['"]).*?\k<quote>/.test(`'world'`); // true
/(?<quote>['"]).*?\k<quote>/.test(`"mixed'`); // false

// Duplicate word finder
/\b(?<word>\w+)\s+\k<word>\b/;

Reference named groups with \k<name>.

With matchAll()

const text = "John Doe, Jane Smith";
const re = /(?<first>\w+) (?<last>\w+)/g;

for (const match of text.matchAll(re)) {
  console.log(match.groups.first); // "John", "Jane"
  console.log(match.groups.last); // "Doe", "Smith"
}

Iterate over all matches.

With replace()

const date = "2023-12-25";

// Use named groups in replacement
date.replace(
  /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/,
  "$<month>/$<day>/$<year>",
); // "12/25/2023"

// Callback with groups
date.replace(/(?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2})/, (match, ...args) => {
  const groups = args[args.length - 1];
  return `${groups.m}/${groups.d}/${groups.y}`;
});

Use in replacement strings.

RegExp Lookbehind Assertions

Positive Lookbehind

// Match digits after $
/(?<=\$)\d+/.exec("$10.53")?.[0]; // "10"

// Match price
/(?<=Price: \$)\d+\.\d{2}/.exec("Price: $19.99");
// ["19.99"]

Match if preceded by pattern.

Negative Lookbehind

// Match "happy" not preceded by "un"
/(?<!un)happy/.test("happy"); // true
/(?<!un)happy/.test("unhappy"); // false

// Match numbers not preceded by $
/(?<!\$)\d+/.exec("$10 and 20")?.[0]; // "0" (from $10)

Match if NOT preceded by pattern.

Before ES2018

// Workaround with capturing group
const match = /\$(\d+)/.exec("$10");
const price = match?.[1]; // "10"

// Or with replace
const num = "$10".replace(/^\$/, "");

No native lookbehind support.

RegExp Unicode Property Escapes

Script Matching

// Match Greek letters (requires /u flag)
/\p{Script=Greek}/u.test("π"); // true
/\p{Script=Greek}/u.test("a"); // false

// Match emoji
/\p{Emoji_Presentation}/gu.test("😀"); // true

⚠️ Requires /u flag.

General Categories

// Match any letter
/\p{Letter}/gu.test("A"); // true

// Match any number
/\p{Number}/gu.test("5"); // true

// Match currency symbols
/\p{Sc}/gu.test("$"); // true
/\p{Sc}/gu.test("€"); // true

// Match punctuation
/\p{Punctuation}/gu.test("!"); // true

Match character categories.

Negated Properties

// Match non-Latin characters
/\P{Script_Extensions=Latin}/gu.test("π"); // true
/\P{Script_Extensions=Latin}/gu.test("a"); // false

// Match non-digits
/\P{Number}/gu.test("a"); // true

Use \P for negation.

Common Properties

Property Matches
\p{Letter} Any letter
\p{Number} Any number
\p{Sc} Currency symbols
\p{Emoji} Emoji characters
\p{Script=Greek} Greek script
\p{White_Space} Whitespace

RegExp s (dotAll) Flag

Basic Usage

// . matches newlines with /s
/foo.bar/s.test("foo\nbar"); // true
/foo.bar/.test("foo\nbar"); // false

// Multiline matching
/^.+$/s.test("line1\nline2"); // true

Make . match newlines.

Check Flag

const re = /pattern/s;
re.dotAll; // true

const re2 = /pattern/;
re2.dotAll; // false

Detect dotAll flag.

Before ES2018

// Workaround with [\s\S] or [^]
/foo[\s\S]bar/.test("foo\nbar"); // true
/foo[^]bar/.test("foo\nbar"); // true

Character class workarounds.

Template Literal Revision

Tagged Templates

function latex(strings) {
  return strings.raw[0];
}

// Illegal escape sequences now allowed
latex`\unicode{00A0}`; // "\unicode{00A0}"

Allow illegal escape sequences.

Windows Paths

function windowsPath(strings) {
  return strings.raw[0];
}

windowsPath`C:\Windows\System32`; // Works!
windowsPath`C:\new\folder`; // Works!

No escaping needed.

strings.raw

function tag(strings, ...values) {
  console.log(strings.raw[0]); // Raw string
  console.log(strings[0]); // Cooked string
}

tag`Line 1\nLine 2`;
// Raw: "Line 1\\nLine 2"
// Cooked: "Line 1\nLine 2"

Access raw template strings.

SharedArrayBuffer & Atomics

SharedArrayBuffer

// Shared memory between workers
const sab = new SharedArrayBuffer(1024);
const view = new Int32Array(sab);

// Share with worker
worker.postMessage({ buffer: sab });

Share memory between threads.

Atomic Operations

const sab = new SharedArrayBuffer(4);
const view = new Int32Array(sab);

// Atomic add
Atomics.add(view, 0, 5);

// Atomic load/store
Atomics.load(view, 0);
Atomics.store(view, 0, 10);

// Wait/notify
Atomics.wait(view, 0, 10);
Atomics.notify(view, 0, 1);

Thread-safe operations.

Browser Requirements

// Requires these headers:
// Cross-Origin-Opener-Policy: same-origin
// Cross-Origin-Embedder-Policy: require-corp

⚠️ Needs cross-origin isolation.

Gotchas

Async Iteration

// ⚠️ Awaits EVERY iteration (even sync values)
for await (const x of [1, 2, 3]) {
  // Slower!
  console.log(x);
}

// ✓ Use regular for...of for sync data
for (const x of [1, 2, 3]) {
  console.log(x);
}

Object Spread

// ⚠️ Shallow copy (nested objects shared)
const copy = { ...original };
copy.nested.value = 42; // Mutates original.nested!

// ✓ Deep copy for nested objects
const deepCopy = JSON.parse(JSON.stringify(obj));

Promise.finally()

// ⚠️ Throwing in finally() overrides original value
Promise.resolve(2)
  .finally(() => {
    throw 99;
  })
  .catch((e) => console.log(e)); // 99 (not 2)

Unicode Property Escapes

// ⚠️ Requires /u flag
/\p{Letter}/gu.test("A"); // ✓ Works
/\p{Letter}/g.test("A"); // ✗ SyntaxError

Browser Support

All ES2018 features are Baseline Widely Available since mid-2020. Supported in:

  • Chrome 63+
  • Firefox 58+
  • Safari 11.1+
  • Edge 79+
  • Node.js 10+

Also see