New JavaScript features I didn’t know about
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 Iterator
s. 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 TypedArray
s 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.