10 New JS Features You Should Know About

The EcmaScript TC39 committee gathered last week in Tokyo and the delegates were really busy.

During these meetings new language proposals are analyzed and moved to one of the five maturity stages they have to go through before they are officially adopted in the language.

This time around, we got quite a few proposals which advanced to next stages, and this is a good time to review them and understand how the JS language might evolve in the near future.

Iterator Helpers

First, Iterator Helpers reached stage 4, and they are now considered feature complete with plans to merge them in the official specification soon.

Iterators are a useful way to represent large or possibly infinite enumerable data sets,

// JS Iterator Protocol

function iterator(start = 0, end = Infinity, step = 1) {
  let idx = start;
  let count = 0;

  return {
    next() {
      let result;

      if (idx < end) {
        result = { value: idx, done: false };
        idx += step;
        count++;

        return result;
      }

      return { value: count, done: true };
    },
  };
}

but they lack helpers which make them as easy to use as Arrays and other finite data structures.

// With No Helper

const iter = iterator(1, 100, 1);
let result = iter.next();

while (!result.done) {
  console.log(result.value);
  result = iter.next();
}

So the proposal introduces a collection of new methods on the Iterator prototype to allow general usage and consumption of iterators.

// With Helper

iterator(1, 100, 1).forEach((val) => {
  console.log(val);
});

The exposed methods are similar to what you are already used to and remember that all of the iterator-producing methods are lazy. They will only consume the iterator when they need the next item from it which is especially important for iterators that never end.

Import Attributes

Import attributes and JSON Modules also reached stage 4.

import json from "./foo.json" with { type: "json" };

These add an inline syntax for module import statements to pass on more information alongside the module specifier. This will have some security benefits, since it provides some safeguards preventing non-executable modules from running.

In import and export statements you can define attributes via the “with” keyword.

import json from "./foo.json" with { type: "json" };
export { val } from "./foo.js" with { type: "javascript" };

For dynamic imports on the other hand, you can define import attributes in the second argument.

import("foo.json", { with: { type: "json" } });

Regex Modifiers

The Regular Expression Modifiers proposal has also reached Stage 4, and will be added to the standard soon.

Being able to control a subset of regular expression flags is a usual capability amongst the majority of regex engines that is commonly used by parsers, syntax highlighters, and other tools.

The modifiers are especially helpful when regular expressions are defined in a context where executable code cannot be evaluated, such as a JSON configuration file.

Iterator Sequencing

Iterator Sequencing is approved in principle and is undergoing validation from tests, implementations, or usage.

This use case is pretty straight forward. Often you have 2 or more iterators, the values of which you would like to consume in sequence, as if they were a single iterator.

let lows = Iterator.from([0, 1, 2, 3]);
let highs = Iterator.from([6, 7, 8, 9]);

A common solution in other libraries is to expose a concat method, and this is what this proposal is suggesting for JavaScript.

let digits = Iterator.concat(lows, [4, 5], highs);

Structs and Shared Structs

The Structs and Shared Structs proposal has advanced to Stage 2, meaning a preferred solution has been selected, though the design is still in draft form and could change. This proposal aims to introduce four shared memory features that would allow developers to write high-performance applications in JavaScript:

  1. Structs;
  2. Shared Structs;
  3. Mutex & Condition;
  4. Unsafe Blocks.

Unshared structs are a refinement of JS classes. They are declarative, like classes, but the layout of struct instances is fixed, so it can’t be extended with new properties. Shared structs on the other hand are even more restrictive since they have to be predictable when accessed in parallel.

Next, let’s go through some smaller additions which still have the potential to impact your developer experience.

Math Sum Precise

The Math.sumPrecise proposal has moved to Stage 3, where it has officially been recommended for implementation. It proposes adding a method to sum multiple values at once. As an additional benefit, it promises more precision when working with floats.

let values = [1e20, 0.1, -1e20];
Math.sumPrecise(values); // 0.1

Extractors

Extractors are now in version 2, and are proposing new destructuring forms for binding and assignment patterns.

const Foo(y) = x; // instance-array destructuring
const Foo([y]) = x; // nested array destructuring
const Foo({y}) = x; // nested object destructuring
const [Foo(y)] = x; // nesting
const { z: Foo(y) } = x; // ...
const Foo(Bar(y)) = x;
const X.Foo(y) = x; // qualified names (i.e. a.b.c)

The main motivation is to provide a mechanism for executing user-defined logic during destructuring, so that operations related to data validation and transformation can be performed in one step.

const InstantExtractor = {
  [Symbol.customMatcher](value) {
    if (value instanceof Temporal.Instant) {
      return [value];
    } else if (value instanceof Date) {
      return [Temporal.Instant.fromEpochMilliseconds(+value)];
    } else if (typeof value === "string") {
      return [Temporal.Instant.from(value)];
    }
  },
};

Promise Try

Promise.try has reached Stage 4 and will be included in the JavaScript standard. This feature provides a new method, which allows developers to wrap a function in a promise for improved error handling and simpler promise chains.

Promise.try(() => {
  console.log("Running synchronous code");
  return "Synchronous result";
})
  .then((result) => {
    console.log("Success:", result);
  })
  .catch((error) => {
    console.error("Error:", error);
  });

It ensures the function is executed immediately and returns a promise, making it easier to handle both synchronous exceptions and asynchronous operations in a consistent manner.

Is Error

The Error.isError proposal, currently at Stage 2, introduces a new method to reliably determine if a value is an instance of a native Error. The proposal addresses issues with existing methods like instanceof error, which are unreliable in various environments.

if (Error.isError(val)) {
  console.log("Woops!");
}

Temporal API

Finally, the Temporal proposal aims to replace the problematic Date object with a more accurate and flexible API for handling dates, times, and time zones.

const today = Temporal.PlainDate.from("2024-10-14");
const tomorrow = today.add({ days: 1 });
const yesterday = today.subtract({ months: 1 });

Remember that these features are not in the standard yet, and some of them might never end up being adoptedt. However, it is fairly common in the web dev space to adopt features early, before they become officially a standard, via a transpiler.

If you feel like you learned something, you should watch some of my youtube videos or subscribe to the newsletter.

Until next time, thank you for reading!