ECMAScript 2024 features you can use now (2024)

ECMAScript 2024 is expected to be finalized in June, but four new JavaScript features are already supported in browsers and Node.js. Here's how to start using them today.

By Matthew Tyson

Contributor, InfoWorld |

ECMAScript 2024 features you can use now (2)

The ECMAScript specification is like a portrait of the JavaScript language that is repainted every year.As is typical of modern JavaScript, the spec and real-world practice move in tandem. The newest version of the spec, ECMAScript 2024, includes seven new JavaScript features and is expected to be finalized in June.This article introduces four of the new features that are already available in browsers and server-side environments, and ready for you to use today:

  • Promise.withResolversis a powerful mechanism for managing asynchronous operations when external control over resolution and rejection is necessary.
  • Object.groupBy and Map.groupBylet you organize collections based on key properties.
  • Atomics.waitAsyncfacilitates safe communication and synchronization between worker threads.
  • String.isWellFormed and String.toWellFormedadd valuable tools for handling user input and network data.

Promise.withResolvers

Let’s start with the new static method on Promise, called withResolvers().JavaScript promises give us various waysto deal with asynchronous operations.The withResolvers() method is used to create the three parts of a Promise: the Promise itself and the resolve() and reject() functions.

The benefit of withResolvers() is that it creates all three as externally exposed references.In cases where you want to create a promise, and also have access to the resolution and rejection of the promise from external code, this is the method to use.

The spec itself is characteristically spartan in its description.The Mozilla documentation provides more detail. The key takeaway is that withResolvers() is equivalent to:

let resolve, reject;const promise = new Promise((res, rej) => { resolve = res; reject = rej;});// use resolve and reject to control the promise

In the above snippet, we declare the resolve and reject references in the enclosing scope, then use them inside the body of the Promise callback to refer to the resolve and reject arguments.In this way, we're getting a handle on the promise callback from outside the callback itself.

The Promise.withResolvers() syntax is more compact and we don't have to declare the resolve and reject separately.With this method, we could write the exact same functionality as above like so:

let { promise, resolve, reject } = Promise.withResolvers();

The essence of this capability is that you use it when you need outside access to resolve() and reject().This isn’t a very common scenario but it happens.Let’s consider a simple example:

function race(operation1, operation2) { const { racePromise, resolve, reject } = Promise.withResolvers(); operation1().then(resolve).catch(reject); operation2().then(resolve).catch(reject); return racePromise;}function fetchData1() { return new Promise((resolve) => { setTimeout(() => resolve("Data from source 1"), 1000); });}function fetchData2() { return new Promise((resolve) => { setTimeout(() => resolve("Data from source 2"), 500); });}race(fetchData1, fetchData2) .then((data) => console.log("Winner:", data)) .catch((error) => console.error("Error:", error));

Here, we have two operations, fetchData1() and fetchData2(), that return promises.They run timeouts, and fetchData2() is always fastest at 500 milliseconds.We use withResolvers() inside the race() function to expose the resolve and reject functions.These functions are then used by a new promise, called racePromise.

We then use the resolve and reject functions to respond to the two fetchData operations.This is interesting because you can see we don’t even provide a callback to racePromise.Instead, we control the promise externally.We use that control to bind the outcome of the other two promises to racePromise.

This is a contrived example, and somewhat unrealistic because the two racing promises do not begin at the same time. The point is to show the essence of how and when to use withResolvers().

Object.groupBy & Map.groupBy

The handy groupBy method is a quick way to organize collections based on a string value.It's a static method on Object and Map, which works on an array-like collection.The groupBy() method takes two arguments: the collection and a callback that operates on it.You get back a new object instance that has string label values as keys, and the corresponding array elements as the value.

So, when you have an array, and you need to divvy up the elements into string-labeled buckets according to some internal criteria, this is the method to use.

This is a fairly common thing that comes up in day-to-day coding.Looking at an example should make it clear.Say we have a collection of dog breeds and their size:

const dogBreeds = [ { breed: 'Shih Tzu', size: 'Toy' }, { breed: 'Leonberger', size: 'Giant' }, { breed: 'King Charles Spaniel', size: 'Toy' }, { breed: 'Great Pyrenees', size: 'Giant' }, { breed: 'Corgi', size: 'Small' }, { breed: 'German Shepherd', size: 'Large' },];

Now, say we want to organize this collection by size.We want to end up with a collection of objects where the keys are the breed size and the values are the dog breed.Normally, we’d write a loop to do this but it's a bit finicky; it seems like there should be a better way. Now, with groupBy(), there is:

groupBy() is that better way:Object.groupBy(dogBreeds, (x) => { return x.size;})This gives us output like so:{ "Toy": [ { "breed": "Shih Tzu", "size": "Toy" }, { "breed": "King Charles Spaniel", "size": "Toy" } ], "Giant": [ { "breed": "Leonberger", "size": "Giant" }, { "breed": "Great Pyrenees", "size": "Giant" } ], "Small": [ { "breed": "Corgi", "size": "Small" } ], "Large": [ { "breed": "German Shepherd", "size": "Large" } ]}

This gives you a simple, functional way to group collections of objects based on some property of the objects themselves.

The groupBy() method takes whatever is returned by the callback and automatically collects all the elements that are equal according to String equality.If the return value is not a string, it’ll be coerced into a string.If it can’t be coerced, it’ll error out.

Atomics.waitAsync

The new Atomics.waitAsync() method is designed for sharing data across worker threads safely. It does not block the main thread like Atomics.wait() does. Because it is used between threads, it relies on the SharedArrayBuffer class.

This class is disabled by default in modern browsers unless security requirements are met.In Node.js, however, the class is enabled by default.

Here’s a simple example for usage in Node.Note that the imports are built into Node, so no NPM installs are required (but note that the import statement is):

// asyncWait.jsconst { Worker, isMainThread, parentPort } = require('worker_threads');const sharedBuffer = new SharedArrayBuffer(4); const int32Array = new Int32Array(sharedBuffer);if (isMainThread) { const worker = new Worker(__filename); async function waitForMessage() { const initialValue = 0; const result = await Atomics.waitAsync(int32Array, 0, initialValue); if (result.async) { console.log("Message received from worker:", int32Array[0]); } else { console.error("Timeout waiting for worker message"); } } waitForMessage(); } else { setTimeout(() => { Atomics.store(int32Array, 0, 1); }, 2000); }

To run this program, just enter: $ node asyncWait.js

The program declares a SharedArrayBuffer (wrapped around an int32Array) and then checks if we are on the main thread. If it is the main thread, we spawn a worker thread.(If you are new to worker threads, here's a good intro.)

The main thread waits for an update from the worker via the Atomics.waitAsync() call.The three args to waitAsync(1, 2, 3) are:

  1. Shared array (int32Array): The shared memory space.
  2. Element to watch (0): The index of the array to wait upon.
  3. Initial value (initialValue = 0): Only notify the main thread if this value is different (i.e., if the worker sets the value to the initial value of 0, a notification will not occur).

String.isWellFormed & String.toWellFormed

User input, bad data, and network glitches are all sources of malformed strings.String.isWellFormed is a sanity check.It determines if a string is UTF-16 valid. UTF-16 is the encoding JavaScript itself uses, so String.isWellFormed() ensures a given string is something JavaScript can handle:

const string1 = "Hello, InfoWorld!";const string2 = "Hello, \uD800world!";console.log(string1.isWellFormed()); // Output: true (well-formed)console.log(string2.isWellFormed()); // Output: false (malformed)

You can learn more about what constitutes valid strings in JavaScript in this section of the Mozilla reference.The bad guys in UTF-16 encoding are known as “lone surrogates.”

The active partner to String.isWellFormed, String.toWellFormed transforms a given string into something valid.Any lone surrogates found will be replaced by U+FFFD, the black diamond question mark character: �.

"Hello, \uD800world!".toWellFormed()// outputs: 'Hello, �world!'

Conclusion

We’ve got a nice collection of new features in ECMAScript 2024.Promise.withResolvers() and Atomics.waitAsync() are more advanced use cases, while groupBy is a convenient addition that often comes in handy, and the new string methods are perfect for certain situations.All of these features are supported for JavaScript in browsers and server-side environments, so you can start using them today.

Next read this:

  • Why companies are leaving the cloud
  • 5 easy ways to run an LLM locally
  • Coding with AI: Tips and best practices from developers
  • Meet Zig: The modern alternative to C
  • What is generative AI? Artificial intelligence that creates
  • The best open source software of 2023

Related:

  • JavaScript
  • Programming Languages
  • Software Development

Matthew Tyson is a founder of Dark Horse Group, Inc. He believes in people-first technology. When not playing guitar, Matt explores the backcountry and the philosophical hinterlands. He has written for JavaWorld and InfoWorld since 2007.

Follow

Copyright © 2024 IDG Communications, Inc.

ECMAScript 2024 features you can use now (2024)

References

Top Articles
Latest Posts
Article information

Author: Merrill Bechtelar CPA

Last Updated:

Views: 5616

Rating: 5 / 5 (50 voted)

Reviews: 89% of readers found this page helpful

Author information

Name: Merrill Bechtelar CPA

Birthday: 1996-05-19

Address: Apt. 114 873 White Lodge, Libbyfurt, CA 93006

Phone: +5983010455207

Job: Legacy Representative

Hobby: Blacksmithing, Urban exploration, Sudoku, Slacklining, Creative writing, Community, Letterboxing

Introduction: My name is Merrill Bechtelar CPA, I am a clean, agreeable, glorious, magnificent, witty, enchanting, comfortable person who loves writing and wants to share my knowledge and understanding with you.