DEV Community

Cover image for JavaScript ES2024 Features You Should Know
Umesh Malik
Umesh Malik

Posted on • Originally published at umesh-malik.com

JavaScript ES2024 Features You Should Know

JavaScript keeps evolving, and ES2024 (ECMAScript 2024) brings several features that solve real problems I encounter in day-to-day frontend development. Here's a practical look at the ones worth adopting now.

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.

Object.groupBy

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', ... }]
// }
Enter fullscreen mode Exit fullscreen mode

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:

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

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

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:

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

Now it's clean:

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

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

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

Real-World Example: Timeout Wrapper

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);
Enter fullscreen mode Exit fullscreen mode

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.

const problematic = 'Hello \uD800 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
Enter fullscreen mode Exit fullscreen mode

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.

RegExp v Flag (Unicode Sets)

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

// 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)
Enter fullscreen mode Exit fullscreen mode

ArrayBuffer Transfer

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

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);
Enter fullscreen mode Exit fullscreen mode

This is useful in performance-critical scenarios like WebGL, audio processing, or working with large binary data in Web Workers.

Atomics.waitAsync

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

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);
Enter fullscreen mode Exit fullscreen mode

Adoption Strategy

These features have strong browser support as of early 2025. Here is my recommendation for adopting them:

  • Use now: Object.groupBy, Promise.withResolvers, String.isWellFormed() — well-supported, immediate productivity gains
  • Use with caution: RegExp v flag — check your browser support matrix
  • Use in Node.js/Workers: ArrayBuffer.transfer, Atomics.waitAsync — more relevant for backend/worker contexts

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+

Originally published at umesh-malik.com

Top comments (0)