5 amazing new JavaScript features in ES15 (2024)
FSMD Fahid Sarker
Senior Software Engineer · July 13, 2024
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.
1. Native array group-by is here
Object.groupBy():
Code.jsconst 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.jsclass 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.jsconst { 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.jsconst 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.jsconst 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.jsconst 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.jsconst 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.jsconst 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.