from Object.groupBy to the Temporal API
ECMAScript is evolving faster than ever. ES2024 and ES2025 bring features the community has been asking for for years: grouping data, better promise control, real date handling without libraries, and structured pattern matching. This article covers every important addition with concrete examples.
Table of Contents
- 1. The TC39 process: how features make it into JavaScript
- 2. Object.groupBy and Map.groupBy (ES2024)
- 3. Promise.withResolvers and promise improvements (ES2024)
- 4. Iterator Helpers: map, filter, take, drop (ES2025)
- 5. Temporal API: date and time done right, finally (ES2025)
- 6. Decorators (ES2025): annotating classes and methods
- 7. More ES2024 features: RegExp v-flag, ArrayBuffer.resize
- 8. Pattern matching: Stage 3 proposal overview
- 9. ES2024 vs. ES2025: features compared
- 10. Summary and adoption strategy
- 11. FAQ
1. The TC39 process: how features make it into JavaScript
ECMAScript features go through a standardized five-stage process at the TC39 committee. ES2024 features and ES2025 features have reached Stage 4, the highest stage, meaning: at least two independent implementations exist, the Test262 test suite entry is complete, and the committee has voted to include it in the specification. Stage 3 features are "candidate ready" and already being implemented by browser engines, but can still see changes. Stage 2 features have a worked-out design but not yet a complete specification.
This context matters for placing the ES2024 and ES2025 features in perspective. Anyone who wants to use the new language features in production needs to know browser compatibility, transpiler support (Babel, TypeScript) and polyfill availability. Most ES2024 features are available in Chrome 117+, Firefox 117+ and Safari 17+. Node.js 21 implements the bulk of them without a flag. TypeScript 5.3+ supports the most important new syntax features. When working without a transpiler, caniuse.com is the most reliable source for current status.
2. Object.groupBy and Map.groupBy (ES2024)
Object.groupBy() is one of the most anticipated ES2024 features. It takes an iterable object and a callback function that returns a key for each element, and groups all elements into an object whose keys are the group keys. This solves an everyday problem that used to be handled with Array.prototype.reduce() and an accumulating helper variable: clunky, hard to read, error-prone. Object.groupBy turns it into a one-line, semantically clear operation.
Map.groupBy() works identically but returns a Map instead of a plain object. The key difference: arbitrary values can be used as keys, not just strings. That's essential when grouping by objects, dates or other non-primitive values. Both methods are non-destructive: they don't modify the original iterable. The resulting object has no prototype (Object.create(null) internally), which means no prototype pollution issues and no accidental overwriting of properties like constructor or toString.
// ES2024: Object.groupBy, replaces complex reduce() patterns
const orders = [
{ id: 1, status: 'pending', amount: 99 },
{ id: 2, status: 'shipped', amount: 249 },
{ id: 3, status: 'pending', amount: 35 },
{ id: 4, status: 'delivered', amount: 180 },
{ id: 5, status: 'shipped', amount: 75 },
];
// Group by status: clean, readable, no reduce() boilerplate
const byStatus = Object.groupBy(orders, order => order.status);
// { pending: [{id:1,...},{id:3,...}], shipped: [{id:2,...},{id:5,...}], delivered: [...] }
// Map.groupBy: use objects or Dates as keys
const products = [
{ name: 'Laptop', category: { id: 1, name: 'Electronics' }, price: 999 },
{ name: 'Phone', category: { id: 1, name: 'Electronics' }, price: 699 },
{ name: 'Desk', category: { id: 2, name: 'Furniture' }, price: 399 },
];
const categories = [...new Set(products.map(p => p.category))];
const byCategory = Map.groupBy(products, p => p.category);
// Map keys are the actual category objects, not stringified versions
// Price range grouping, replaces multiple filter() chains
const byPriceRange = Object.groupBy(products, ({ price }) => {
if (price < 400) return 'budget';
if (price < 800) return 'mid-range';
return 'premium';
});
3. Promise.withResolvers and promise improvements (ES2024)
Promise.withResolvers() is another practical ES2024 feature that eliminates a common boilerplate pattern. Previously, to access resolve and reject outside the promise constructor, you had to use an awkward construction: extracting the callbacks into outer variables by calling the constructor with a function that sets those variables. This "deferred" pattern is implemented in many libraries, but wasn't part of the language. Promise.withResolvers() returns an object with three properties: promise, resolve and reject.
This ES2024 feature is especially useful when implementing event queues, waiting on external events in async contexts, or building test utilities that need to wait for asynchronous callbacks. A concrete example: a WebSocket client that needs to wait for the first "open" message can use Promise.withResolvers() to create a promise whose resolve function is registered directly as an event listener, without the roundabout construction using outer variables. That makes the code more compact and its intent clearer.
// ES2024: Promise.withResolvers, the Deferred pattern, standardized
const { promise, resolve, reject } = Promise.withResolvers();
// Pass resolve directly as an event callback, no wrapper needed
document.addEventListener('DOMContentLoaded', resolve, { once: true });
await promise; // resolves when DOM is ready
// WebSocket readiness: clean pattern without outer variable hoisting
async function connectWebSocket(url) {
const ws = new WebSocket(url);
const { promise: ready, resolve: onOpen, reject: onError } = Promise.withResolvers();
ws.addEventListener('open', onOpen, { once: true });
ws.addEventListener('error', onError, { once: true });
await ready; // throws if 'error' fires first
return ws;
}
// Timeout race, combine with AbortController
async function fetchWithTimeout(url, ms) {
const { promise: timeout, reject: abort } = Promise.withResolvers();
const timer = setTimeout(() => abort(new Error(`Timeout after ${ms}ms`)), ms);
try {
return await Promise.race([fetch(url), timeout]);
} finally {
clearTimeout(timer);
}
}
4. Iterator Helpers: map, filter, take, drop (ES2025)
Iterator Helpers are one of the most important ES2025 features for functional data processing. The problem: iterators in JavaScript, from generators to Map, Set and DOM collection iterators, are primitive. You first have to convert them into arrays to use .map(), .filter() or .reduce(). That conversion isn't just cumbersome, it's expensive too: it materializes the entire collection in memory. Iterator Helpers enable lazy chaining directly on the iterator: filter and map only run once the result is actually consumed.
The methods are defined as prototype methods on the built-in iterator prototype: Iterator.prototype.map, Iterator.prototype.filter, Iterator.prototype.take, Iterator.prototype.drop, Iterator.prototype.flatMap, Iterator.prototype.reduce, Iterator.prototype.toArray and Iterator.prototype.forEach. All methods except reduce, toArray and forEach are lazy: they return a new iterator without materializing the chain. That makes ES2025 Iterator Helpers especially valuable for large datasets, infinite generators and streaming scenarios.
5. Temporal API: date and time done right, finally (ES2025)
The Temporal API is the most ambitious ES2025 feature and solves a problem that has plagued JavaScript developers for decades: the built-in Date object is fundamentally flawed. It has no timezone-aware arithmetic, always internally represents UTC as a millisecond timestamp, has no sensible arithmetic for months and years, and its API is historically inconsistent. Libraries like Moment.js, date-fns and Luxon exist mainly because Date is so bad. Temporal replaces all of that with a complete, modern date-time library built into the language itself.
The Temporal types are deliberately separated: Temporal.PlainDate for a date without time or timezone, Temporal.PlainTime for a time without a date, Temporal.PlainDateTime for date plus time without a timezone, Temporal.ZonedDateTime for date plus time plus timezone, and Temporal.Instant for an absolute point in time (like Date). The separation forces you to explicitly think about timezones, the most common mistake in date handling. Arithmetic with Temporal is correct: adding a month always yields a valid end-of-month date, even in February.
// ES2025: Temporal API, correct date/time arithmetic without libraries
// Note: Use polyfill (@js-temporal/polyfill) until fully shipped
// PlainDate: date without time or timezone
const today = Temporal.Now.plainDateISO(); // '2026-05-10'
const nextMonth = today.add({ months: 1 }); // '2026-06-10'
const endOfQuarter = today.with({ month: 6, day: 30 }); // '2026-06-30'
// ZonedDateTime: date + time + timezone, correct DST handling
const meeting = Temporal.ZonedDateTime.from({
year: 2026, month: 6, day: 15,
hour: 10, minute: 0,
timeZone: 'Europe/Berlin'
});
const inNewYork = meeting.withTimeZone('America/New_York');
console.log(inNewYork.toString()); // '2026-06-15T04:00:00-04:00[America/New_York]'
// Duration arithmetic, handles month-boundary correctly
const start = Temporal.PlainDate.from('2026-01-31');
const oneMonth = start.add({ months: 1 }); // '2026-02-28', not March 3rd
// Compare and sort dates
const dates = ['2026-03-15', '2026-01-01', '2026-12-31']
.map(d => Temporal.PlainDate.from(d))
.sort(Temporal.PlainDate.compare); // built-in comparator
// Duration between two dates
const diff = today.until(nextMonth, { largestUnit: 'day' });
console.log(`${diff.days} days until next month`); // '31 days'
6. Decorators (ES2025): annotating classes and methods
Decorators are one of the longest-awaited ES2025 features, and one TypeScript developers already know, though from a different, older spec version. The ES2025 decorators specification is fundamentally reworked and differs from the TypeScript decorators under experimentalDecorators. The new spec is more precise, safer and more consistent. Decorators can be applied to classes, class methods, getters, setters, fields and accessor fields. They are functions that run at class definition time, not at execution time.
The practical benefit of decorators: cross-cutting concerns like logging, memoization, validation, rate limiting and caching can be implemented as reusable functions and applied declaratively to classes, without AOP frameworks. A @memoize decorator on a class method caches the result based on the arguments, no code duplication, no manual wrapping. A @readonly decorator on a field makes it non-writable. A @validate decorator on a setter checks the value before assignment. Decorator composition, multiple decorators on one method, is clearly defined and applied from outside in.
7. More ES2024 features: RegExp v-flag, ArrayBuffer.resize
The RegExp v-flag (Unicode Sets) is a quiet but important ES2024 feature. It enables extended Unicode support: nested character classes ([a-z&&[^aeiou]] for consonants), set operations in character classes, and correct handling of Unicode strings with grapheme clusters. For international applications that validate names, addresses or natural-language input, the v-flag is an important upgrade over the u-flag. The syntax is backward compatible, existing regex expressions are not affected.
ArrayBuffer.prototype.resize() and ArrayBuffer.prototype.transfer() are ES2024 features for efficient binary data handling. Instead of having to allocate a new ArrayBuffer and copy data whenever the size changes, you can grow or shrink an existing buffer in place. This is especially relevant for WebAssembly interop, audio processing and network streaming, where buffer sizes need to be adjusted dynamically. The transfer() method enables zero-copy transfer of a buffer into another context, useful for web workers that need to exchange data without copying.
// ES2024: RegExp v-flag, Unicode Sets and nested character classes
// Consonants only (letters minus vowels):
const consonants = /^[a-z&&[^aeiou]]+$/v;
console.log(consonants.test('bcdf')); // true
console.log(consonants.test('hello')); // false, contains vowels
// Unicode property escapes with set operations
const greekLetters = /^\p{Script=Greek}+$/v;
const notASCII = /^[^\p{ASCII}]+$/v;
// String properties (new in v-flag):
const emojiSeq = /^\p{RGI_Emoji}+$/v; // matches emoji grapheme clusters correctly
// ES2025: Iterator Helpers, lazy transformation chains
function* fibonacci() {
let [a, b] = [0, 1];
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
// Take first 10 even Fibonacci numbers, no Array materialization until .toArray()
const evenFibs = fibonacci()
.filter(n => n % 2 === 0) // lazy filter
.take(10) // lazy take
.toArray(); // materialize only here
console.log(evenFibs); // [0, 2, 8, 34, 144, 610, 2584, 10946, 46368, 196418]
// Iterator.from() wraps any iterable
const mapIter = Iterator.from(new Map([['a', 1], ['b', 2]]))
.map(([key, val]) => `${key}=${val}`)
.toArray(); // ['a=1', 'b=2']
8. Pattern matching: Stage 3 proposal overview
Pattern matching is not yet a finalized ES2025 feature, but it's in Stage 3 and considered by many to be the most important upcoming JavaScript feature. The match statement enables structural pattern matching similar to Rust, Haskell or Scala: a value is compared against patterns, and the first matching branch runs. Unlike switch, pattern matching doesn't just check equality, it can look deep inside objects, check types, apply guards and destructure values.
The proposed syntax uses match (value) { when Pattern: expression }. Patterns can combine literals, variable bindings, array patterns, object patterns and guards (when condition). A match expression on a fetch response could match against { status: 200, body }, { status: 404 } and a default case, each branch with its own expression. This eliminates deep if-else chains when handling API responses, Redux actions or discriminated unions. TypeScript users know the concept from exhaustive type narrowing, but pattern matching turns it into a runtime check in JavaScript itself.
9. ES2024 vs. ES2025: features compared
The difference between ES2024 features and ES2025 features lies both in scope and in current availability. ES2024 is fully specified and implemented in modern runtimes. ES2025 includes larger features like the Temporal API and decorators, which are still in the final implementation phase.
| Feature | Spec | Chrome | Node.js |
|---|---|---|---|
| Object.groupBy / Map.groupBy | ES2024 | 117+ | 21+ |
| Promise.withResolvers | ES2024 | 119+ | 22+ |
| RegExp v-flag | ES2024 | 112+ | 20+ |
| Iterator Helpers | ES2025 | 122+ | 22+ |
| Temporal API | ES2025 | Polyfill | Polyfill |
| Decorators | ES2025 | Flag/Polyfill | Flag |
The adoption strategy for ES2024 and ES2025 features depends on the target environment. Features with broad browser support like Object.groupBy and Promise.withResolvers can be used today without a transpiler or polyfill, as long as IE support isn't required. For Temporal and decorators, the @js-temporal/polyfill or the Babel decorators plugin is the recommended path until native support lands.
Mironsoft
Modern JavaScript development and ECMAScript adoption
Want to roll out modern JavaScript features safely?
We help you integrate ES2024 and ES2025 features into existing codebases step by step, with transpiler setup, polyfill strategy and code review for modern JavaScript patterns.
Codebase migration
Modernize existing code to use modern ES2024/ES2025 patterns
Build setup
Configure Babel, TypeScript and Vite for new JavaScript features
Team training
Workshop on ES2024/ES2025 features for JavaScript development teams
10. Summary and adoption strategy
The ES2024 and ES2025 features aren't toys for early adopters, they're answers to real, long-known JavaScript deficiencies. Object.groupBy and Map.groupBy replace complex reduce() patterns. Promise.withResolvers standardizes the deferred pattern. Iterator Helpers bring lazy transformation to all iterators without array conversion. The Temporal API makes date libraries obsolete for most use cases. Decorators enable declarative cross-cutting concerns without framework overhead.
The adoption strategy follows the level of availability: use ES2024 features without a transpiler directly in modern projects. Use ES2025 features with a polyfill or transpiler plugin depending on the browser target. TypeScript projects benefit through type inference and early syntax support. The most important step is regularly reading the TC39 proposals repo and the MDN compatibility tables, since the JavaScript language keeps evolving with several significant additions per year.
ES2024 & ES2025 Features: the essentials at a glance
ES2024: usable today
Object.groupBy, Map.groupBy, Promise.withResolvers, RegExp v-flag, ArrayBuffer.resize: all available in Chrome 117+ and Node.js 21+ without a polyfill.
ES2025: with polyfill
Iterator Helpers (Chrome 122+), Temporal API (@js-temporal/polyfill), decorators (Babel plugin): Stage 4 finalized, but not yet natively available everywhere.
Temporal API
Replaces date libraries: PlainDate, ZonedDateTime, correct month arithmetic, DST-aware timezone conversion. Polyfill: @js-temporal/polyfill.
Adoption strategy
Regularly follow TC39 proposals and MDN compatibility tables. Introduce features step by step based on availability, use TypeScript for early type support.