❀✿❀ SuperLaserNino ✿❀✿

New JavaScript features I didn’t know about

5 June 2025

571 words

Now that going bouldering once a week no longer counts as having a personality, maybe I should try to stand out by being that guy who says “ECMAScript” instead of “JavaScript”.

Anyway, here are some new(-ish) JavaScript features that I recently discovered.

groupBy (2024)

There are now built-in functions to group array elements. With Object.groupBy(items) the result will be a normal object, and with Map.groupBy(items) the result will be a Map.

Both of these have been widely available since March 2024, so they’re ready to use.

Iterator helpers (2025)

I’ve always been irrationally upset that JavaScript’s iterators aren’t as ergonomic as Rust’s. In Rust, you write something like

things.iter()
    .map(|thing| do_stuff(&thing))
    .filter(|thing| do_stuff2(&thing))
    .map(|thing| do_stuff3(&thing))
    .collect();

and that turns your original things into a lazy iterator, then does a bunch of operations, and then collect() turns it back into a Vec. The iterators just go through the items one by one instead of allocating a whole pointless intermediate array.

If you do the same thing in JavaScript, it looks like

things
  .map((thing) => do_stuff(thing))
  .filter((thing) => do_stuff2(thing))
  .map((thing) => do_stuff3(thing));

Semantically that does the same thing as the Rust code, but each map and filter allocates an intermediate array. If the initial things is large, this could theoretically be a performance issue. So far, the way to optimise the above code would be to just use for loops, but for loops are cringe, so the fine folks at TC39 have blessed us with the same helper methods found on Array, but for Iterators. So you can write

Iterator.from(things)
  .map((thing) => do_stuff(thing))
  .filter((thing) => do_stuff2(thing))
  .map((thing) => do_stuff3(thing))
  .toArray();

and bask in the glory of some beautiful premature optimisation.

These methods are available in all modern browsers as of 2025, so are also ready to use.

Error.isError (2025/2026?)

If you ever get annoyed at catch (error: unknown) in TypeScript, there is now a reliable way of checking whether something is an error, and that is Error.isError(error). It’s more readable than

error &&
  typeof error === "object" &&
  "message" in error &&
  typeof error.message === "string";

and it’s more reliable than error instanceof Error, because the instanceof check apparently fails when the error was constructed in a different realm.

Error.isError is available in all modern browsers, except that Safari will currently return false for DOMException objects, which it shouldn’t. So this one is almost ready to use.

TypedArray.prototype.subarray

This one’s actually been around for 10 years, but I only discovered it recently (partly because I’ve never really used TypedArrays before). Unlike TypedArray.prototype.slice, TypedArray.prototype.subarray creates a new TypedArray that points to the same block of memory used by the original, and the data isn’t copied. I found this useful when I was writing (part of) a disassembler, which looked kind of like this:

function decode_instruction(data: Uint8Array): [Instruction, Uint8Array] {
  // Some horrifying ungodly logic
  return [
    my_instruction,
    data.subarray(however_many_bytes_my_instruction_has_taken_up),
  ];
}

function* decode_instructions(data: Uint8Array): Generator<Instruction> {
  let rest = data;
  while (rest.length > 0) {
    const [instruction, new_rest] = decode_instruction(rest);
    yield instruction;
    rest = new_rest;
  }
}

With TypedArray.prototype.slice, this would have allocated a whole new copy of the input data after each instruction was decoded. Without subarray, I’d have had to just keep a reference to the original array and pass around an offset index, and that would have been so inconvenient.