DevZone Logo

5 amazing new JavaScript features in ES15 (2024)

FS

MD Fahid Sarker

Senior Software Engineer · July 13, 2024


-
-
Share
Bookmark

2024: Another incredible year of brand-new JS feature upgrades with ES15.

From sophisticated async features to syntactic array sugar and modern regex, JavaScript coding is now easier and faster than ever.

es15

1. Native array group-by is here

Object.groupBy():

Code.js
const fruits = [ { name: "pineapple 🍍", color: "🟡" }, { name: "apple 🍎", color: "🔴" }, { name: "banana 🍌", color: "🟡" }, { name: "strawberry 🍓", color: "🔴" }, ]; const groupedByColor = Object.groupBy(fruits, (fruit, index) => fruit.color); // print it out console.log(groupedByColor);
Output
{ "🟡": [ { name: "pineapple 🍍", color: "🟡" }, { name: "banana 🍌", color: "🟡" } ], "🔴": [ { name: "apple 🍎", color: "🔴" }, { name: "strawberry 🍓", color: "🔴" } ] }

Literally the only thing keeping dinosaur Lodash alive — no more!

To be honest, I was expecting a new instance method like Array.prototype.groupBy but they made it static for whatever reason.

2. Resolve promise from outside — modern way

With Promise.withResolvers().

It’s prevalent to resolve promises externally and before we had to do it with a Deferred class:

Code.js
class Deferred { constructor() { this promise = new Promise((resolve, reject) => { this.resolve = resolve; this.reject = reject; }); } } const deferred = new Deferred(); deferred.resolve();

or we had to use something like the differed package from npm.

But now with Promise.withResolvers() from ES15:

Code.js
const { promise, resolve, reject } = Promise.withResolvers();

See how I use to rapidly promisify an event stream — awaiting an observable:

Code.js
// data-fetcher.js // ... const { promise, resolve, reject } = Promise.withResolvers(); function startListening() { eventStream.on("data", (data) => { resolve(data); }); } async function getData() { return await promise; } // client.js const { startListening, getData } = require("./data-fetcher"); startListening(); // listen for single data event const data = await getData();

3. Buffer performance upgrades

Buffers are tiny data stores to store temporary data your app generates.

They make it incredibly easy to transfer and process data across various stages in a pipeline.

Pipelines like:

  • File processing: Input file → buffer → process → new buffer → output file
  • Video streaming: Network response → buffer → display video frame
  • Restaurant queues: Receive customer → queue/buffer → serve customer
Code.js
const fs = require("fs"); const { Transform } = require("stream"); const inputFile = "input.txt"; const outputFile = "output.txt"; const inputStream = fs.createReadStream(inputFile, "utf-8"); const transformStream = new Transform({ transform(chunk) { // process data }, }); const outputStream = fs.createWriteStream(outputFile); // start pipeline inputStream.pipe(transformStream).pipe(outputStream);

With buffers, each stage processes data at different speeds independent of each other.

But what happens when the data moving through the pipeline exceeds the buffer capacity?

Before we’d have to copy all the current data’s buffer to a bigger buffer.

Terrible for performance, especially when there’s gonna be a LOT of data in the pipeline.

ES15 gives us a solution to this problem: Resizable array buffers.

Code.js
const resizableBuffer = new ArrayBuffer(1024, { maxByteLength: 1024 ** 2 }); // resize to 2048 bytes resizableBuffer.resize(1024 * 2);

4. Asynchronous upgrades

Atomics.waitAsync(): Another powerful async coding feature in ES2024:

It’s when 2 agents share a buffer…

And agent 1 “sleeps” and waits for agent 2 to complete a task.

When agent 2 is done, it notifies using the shared buffer as a channel.

Code.js
const sharedBuffer = new SharedArrayBuffer(4096); const bufferLocation = new Int32Array(sharedBuffer); // initial value at buffer location bufferLocation[37] = 0x1330; async function doStuff() { // agent 1: wait on shared buffer location until notify Atomics.waitAsync(bufferLocation, 37, 0x1330).then( (r) => {} /* handle arrival */ ); } function asyncTask() { // agent 2: notify on shared buffer location const bufferLocation = new Int32Array(sharedBuffer); Atomics.notify(bufferLocation, 37); }

You’d be absolutely right if you thought this similar to normal async/await.

But the biggest difference: The 2 agents can exist in completely different code contexts — they only need access to the same buffer.

And: multiple agents can access or wait on the shared buffer at different times — and any one of them can notify to “wake up” all the others.

It’s like a P2P network; async/await is like client-server request-response.

Code.js
const sharedBuffer = new SharedArrayBuffer(4096); const bufferLocation = new Int32Array(sharedBuffer); bufferLocation[37] = 0x1330; // received shared buffer from postMessage() const code = ` var ia = null; onmessage = function (ev) { if (!ia) { postMessage("Aux worker is running"); ia = new Int32Array(ev.data); } postMessage("Aux worker is sleeping for a little bit"); setTimeout(function () { postMessage("Aux worker is waking"); Atomics.notify(ia, 37); }, 1000); }`; async function doStuff() { // agent 1: exists in a Worker context const worker = new Worker( "data:application/javascript," + encodeURIComponent(code) ); worker.onmessage = (event) => { // log event }; worker.postMessage(sharedBuffer); Atomics.waitAsync(bufferLocation, 37, 0x1330).then( (r) => {} /* handle arrival */ ); } function asyncTask() { // agent 2: notify on shared buffer location const bufferLocation = new Int32Array(sharedBuffer); Atomics.notify(bufferLocation, 37); }

5. Regex v flag & set operations

A brand new feature to make regexes much cleaner and more intuitive.

Finding and manipulating complex strings using expressive patterns — with the help of set operations:

Code.js
// A and B are character class, like [a-z] // difference: matches A but not B [A--B] // intersection: matches both A & B [A&&B] // nested character class [A--[0-9]]

To match ever-increasing sets of Unicode characters, like:

  • Emojis: 😀, ❤️, 👍, 🎉, etc.
  • Accented letters: é, à, ö, ñ, etc.
  • Symbols and non-Latin characters: ©, ®, €, £, µ, ¥, etc

So here we use Unicode regex and the v flag to match all Greek letters:

Code.js
const regex = /[p{Script_Extensions=Greek}&& p{Letter}]/v;

Final thoughts

Overall ES15 is a significant leap for JavaScript with several features essential for modern development.

Helping you to write cleaner code with greater conciseness, expressiveness, and clarity.

Found the blog helpful? Consider sharing it with your friends.
Buy Me a Coffee

JavaScriptES15ECMAScriptFrontend

Related Blogs

Blogs that you might find interesting based on this blog.