Skip to main content

ES2024 Features You'll Actually Use in JavaScript

The most impactful ES2024 features: Array grouping, Promise.withResolvers, well-formed Unicode strings, and the RegExp v flag — with practical examples.

5 min read
JavaScript ES2024 features overview showing Object.groupBy, Promise.withResolvers, Unicode strings, and RegExp v flag

The ES2024 features below are the ones I’ve actually reached for in day-to-day frontend work — not spec trivia nobody ships. Here’s a practical look at the ones worth adopting now. If you write TypeScript day to day, these pair well with my complete guide to TypeScript utility types. Related reading: React Performance Optimization and SvelteKit vs Next.js in 2026.

What Is ES2024?

ES2024 is the JavaScript language spec update — ECMAScript 2024, ratified June 2024 — that adds native array grouping, deferred promises, well-formed Unicode string checks, and Unicode set operations in regex.

TL;DR

  • Object.groupBy / Map.groupBy kill the hand-rolled reduce grouping utility every codebase has one of.
  • Promise.withResolvers() gives you resolve/reject outside the executor — cleaner event-driven and timeout code.
  • String.isWellFormed() / toWellFormed() catch lone surrogates before they break encodeURIComponent or a search index.
  • The RegExp v flag adds set subtraction and intersection for real Unicode-aware pattern matching.
  • All four are shipped and stable in every major browser and Node.js 22+ — safe to use today, no polyfill needed.

ES2024 IN ONE GLANCE

The most useful parts of ES2024 are not abstract spec trivia. They remove recurring boilerplate and long-standing edge-case pain.

DATA SHAPING

Object.groupBy and Map.groupBy

Native grouping replaces one of the most repeated `reduce` utilities in app code.

  • Cleaner collection transforms
  • Less custom utility code

ASYNC CONTROL

Promise.withResolvers

The deferred-promise pattern becomes explicit and much less awkward.

  • Better event-driven coordination
  • Cleaner timeout wrappers

TEXT SAFETY

Well-formed Unicode strings

These methods catch malformed text before it blows up encoding and data pipelines.

  • Safer `encodeURIComponent` usage
  • Better handling of user-generated input

REGEX

The `v` flag upgrades Unicode matching

Set operations and better Unicode semantics make regex work more expressive for global products.

  • Set subtraction
  • Set intersection
  • Better emoji and script handling

LOW-LEVEL

ArrayBuffer transfer and Atomics.waitAsync

These matter more for workers, performance-sensitive apps, and systems-style JS than for everyday component code.

  • Better ownership transfer
  • Non-blocking shared-memory coordination

What Are the Biggest ES2024 Features?

Five ES2024 features actually change how you write everyday code: Object.groupBy/Map.groupBy, Promise.withResolvers, well-formed Unicode string methods, the RegExp v flag, and ArrayBuffer.transfer() alongside Atomics.waitAsync() for worker-heavy apps. The rest of this post walks through each one with real code.

Object.groupBy and Map.groupBy

Grouping arrays by a property has been a common utility function in every project I’ve worked on. ES2024 makes it native.

Before ES2024 (manual reduce)ES2024 (Object.groupBy)
Write and maintain a groupBy helper per projectZero-dependency, built into the language
Easy to get the accumulator initialization wrongNo accumulator to manage
Returns a plain mutable object either wayReturns a null-prototype object, safer against prototype pollution
No non-string-key variant without extra codeMap.groupBy handles non-string keys natively

Object.groupBy

JavaScript
const products = [
  { name: 'Laptop', category: 'electronics', price: 999 },
  { name: 'Shirt', category: 'clothing', price: 29 },
  { name: 'Phone', category: 'electronics', price: 699 },
  { name: 'Jeans', category: 'clothing', price: 59 },
  { name: 'Tablet', category: 'electronics', price: 449 },
];

const grouped = Object.groupBy(products, (product) => product.category);

// Result:
// {
//   electronics: [{ name: 'Laptop', ... }, { name: 'Phone', ... }, { name: 'Tablet', ... }],
//   clothing: [{ name: 'Shirt', ... }, { name: 'Jeans', ... }]
// }

This replaces the reduce boilerplate we’ve all written dozens of times. At Expedia, we had a utility called groupBy that did exactly this — now it’s built in.

Map.groupBy

When you need non-string keys, use Map.groupBy:

JavaScript
const grouped = Map.groupBy(products, (product) =>
  product.price > 500 ? 'premium' : 'budget'
);

grouped.get('premium'); // [Laptop, Phone]
grouped.get('budget');  // [Shirt, Jeans, Tablet]

Promise.withResolvers

This is one of those features that eliminates an awkward pattern. Previously, to get external access to resolve and reject, you had to do this:

JavaScript
// Before ES2024
let resolve, reject;
const promise = new Promise((res, rej) => {
  resolve = res;
  reject = rej;
});

Now it’s clean:

JavaScript
// ES2024
const { promise, resolve, reject } = Promise.withResolvers();

// Use it in event-driven code
button.addEventListener('click', () => resolve('clicked'), { once: true });
const result = await promise;

This is particularly useful for wrapping callback-based APIs or building custom async coordination patterns.

Real-World Example: Timeout Wrapper

JavaScript
function withTimeout(asyncFn, ms) {
  const { promise: timeoutPromise, reject } = Promise.withResolvers();
  const timer = setTimeout(() => reject(new Error('Timeout')), ms);

  return Promise.race([
    asyncFn().finally(() => clearTimeout(timer)),
    timeoutPromise,
  ]);
}

// Usage
const data = await withTimeout(() => fetch('/api/data'), 5000);

Well-Formed Unicode Strings

String.prototype.isWellFormed() and String.prototype.toWellFormed() help you deal with lone surrogates — characters that can cause issues in encodeURIComponent and other APIs.

JavaScript
const problematic = 'Hello  World';

problematic.isWellFormed();  // false
problematic.toWellFormed();  // 'Hello � World' (lone surrogate replaced)

// Safe encoding
const safeStr = input.isWellFormed() ? input : input.toWellFormed();
const encoded = encodeURIComponent(safeStr); // No more URIError

At Tekion, we dealt with user-generated content from dealership forms in multiple languages. Malformed Unicode caused silent failures in our search indexing pipeline. These methods would have caught those issues early — a validation guard before the string ever reaches encodeURIComponent or a downstream search index is a five-minute fix once you know the method exists.

The failure mode is nasty precisely because it’s silent. A lone surrogate doesn’t throw when you create the string — it throws (or worse, produces mojibake) three layers downstream, in a URL encoder, a JSON serializer, or a database driver that assumes valid UTF-16. isWellFormed() lets you check at the boundary, right where the untrusted input enters your system, instead of chasing the failure through a stack trace that has nothing to do with the real cause.

RegExp v Flag (Unicode Sets)

The new v flag replaces the u flag with extended capabilities for matching Unicode characters and set operations.

JavaScript
// Match any emoji
const emojiRegex = /p{Emoji}/v;
emojiRegex.test('👋'); // true

// Set subtraction: match Greek letters except specific ones
const regex = /[p{Script=Greek}--[αβγ]]/v;
regex.test('δ'); // true
regex.test('α'); // false

// Set intersection: match characters that are both ASCII and digits
const asciiDigits = /[p{ASCII}&&p{Number}]/v;
asciiDigits.test('5'); // true
asciiDigits.test('٥'); // false (Arabic-Indic digit)

ArrayBuffer Transfer

ArrayBuffer.prototype.transfer() lets you efficiently move ownership of a buffer’s memory, similar to Rust’s ownership model.

JavaScript
const buffer = new ArrayBuffer(1024);
const transferred = buffer.transfer();

buffer.byteLength;      // 0 (original is now detached)
transferred.byteLength; // 1024

// Resize during transfer
const resized = buffer.transfer(2048);

This is useful in performance-critical scenarios like WebGL, audio processing, or working with large binary data in Web Workers. Before transfer(), moving a large buffer to a worker meant either copying it (expensive) or using postMessage’s transferable-objects list (works, but ties you to the message-passing API). transfer() gives you the same zero-copy ownership move as a plain method call, so you can hand off memory inside regular application code, not just across a postMessage boundary.

Atomics.waitAsync

Atomics.waitAsync() provides non-blocking waiting on shared memory, enabling better coordination between the main thread and Web Workers.

JavaScript
const sharedBuffer = new SharedArrayBuffer(4);
const sharedArray = new Int32Array(sharedBuffer);

// Non-blocking wait on main thread
const result = Atomics.waitAsync(sharedArray, 0, 0);
result.value.then(() => {
  console.log('Worker signaled completion');
});

// In worker: Atomics.notify(sharedArray, 0);

How Do I Choose Which ES2024 Feature to Adopt First?

These features have strong browser support as of early 2025. Here is my recommendation for adopting them: start with the ones that remove code you already maintain (Object.groupBy, Promise.withResolvers, well-formed Unicode checks), and treat the runtime-dependent ones (v flag, ArrayBuffer.transfer(), Atomics.waitAsync()) as opportunistic upgrades once your support matrix clears them.

ADOPTION GUIDE

Not every ES2024 feature deserves the same rollout urgency. Some are immediate quality-of-life wins. Others depend more on your runtime targets.

USE NOW

High-confidence additions for everyday codebases

These features are the easiest to justify because they remove real boilerplate or failure modes with little conceptual overhead.

  • `Object.groupBy` and `Map.groupBy`
  • `Promise.withResolvers`
  • `String.isWellFormed()` and `toWellFormed()`

EVALUATE FIRST

Features that depend more on runtime context

These are useful, but rollout should follow your browser support matrix or your worker and backend environment.

  • RegExp `v` flag in browser-sensitive apps
  • `ArrayBuffer.transfer()` for perf-heavy workloads
  • `Atomics.waitAsync()` when you actually use shared memory and workers

FAQ

Questions readers usually have

Sources

Key Takeaways

  • Object.groupBy eliminates one of the most common utility functions in JavaScript projects
  • Promise.withResolvers cleans up the deferred promise pattern
  • Well-formed Unicode methods prevent silent encoding failures
  • The RegExp v flag enables powerful Unicode-aware pattern matching
  • These features are production-ready in modern browsers and Node.js 22+
Share this article:
X LinkedIn

Keep reading

Get new posts on AI, Claude Code & LLMs

New deep-dives on AI engineering, Claude Code, and developer tooling — follow along however you prefer.