Major Changes in ES2024-2025
JavaScript adds new features every year through the ECMAScript specification. ES2024 and ES2025 include features that significantly boost developer productivity. This article covers the key features you can use in production right away, with examples.
Object.groupBy / Map.groupBy (ES2024)
These are static methods that group array elements by a given criterion. You can now use lodash’s groupBy natively.
const products = [
{ name: "MacBook Pro", category: "laptop", price: 2499 },
{ name: "iPad", category: "tablet", price: 1199 },
{ name: "Galaxy Book", category: "laptop", price: 1799 },
{ name: "Galaxy Tab", category: "tablet", price: 799 },
{ name: "LG Gram", category: "laptop", price: 1599 },
];
// Object.groupBy — group into a plain object
const byCategory = Object.groupBy(products, (p) => p.category);
console.log(byCategory);
// {
// "laptop": [
// { name: "MacBook Pro", category: "laptop", price: 2499 },
// { name: "Galaxy Book", category: "laptop", price: 1799 },
// { name: "LG Gram", category: "laptop", price: 1599 },
// ],
// "tablet": [
// { name: "iPad", category: "tablet", price: 1199 },
// { name: "Galaxy Tab", category: "tablet", price: 799 },
// ]
// }
// Group by price range
const byPriceRange = Object.groupBy(products, (p) => {
if (p.price >= 2000) return "premium";
if (p.price >= 1000) return "mid-range";
return "budget";
});
console.log(Object.keys(byPriceRange));
// ["premium", "mid-range", "budget"]
// Map.groupBy — group into a Map object (supports object keys)
const byMap = Map.groupBy(products, (p) => p.category);
console.log(byMap.get("laptop").length); // 3
| Method | Return type | Key type | Use case |
|---|---|---|---|
Object.groupBy | Plain object | String/Symbol | Simple grouping |
Map.groupBy | Map | Any type | When using objects as keys |
Promise.withResolvers (ES2024)
A utility that extracts resolve and reject functions externally when creating a Promise. Especially useful when converting callback-based code to Promises.
// Old approach: needed separate variable declarations for external resolve/reject
let externalResolve;
let externalReject;
const oldPromise = new Promise((resolve, reject) => {
externalResolve = resolve;
externalReject = reject;
});
// ES2024 approach: solved in one line
const { promise, resolve, reject } = Promise.withResolvers();
// Real-world example: converting event-based code to Promises
function waitForEvent(element, eventName, timeoutMs = 5000) {
const { promise, resolve, reject } = Promise.withResolvers();
const timer = setTimeout(() => {
reject(new Error(`${eventName} event timed out after ${timeoutMs}ms`));
}, timeoutMs);
element.addEventListener(eventName, (event) => {
clearTimeout(timer);
resolve(event);
}, { once: true });
return promise;
}
// Usage example (browser environment)
// const event = await waitForEvent(button, "click", 3000);
// Real-world example: queue-based task processing
class TaskQueue {
#queue = [];
enqueue(task) {
const { promise, resolve, reject } = Promise.withResolvers();
this.#queue.push({ task, resolve, reject });
this.#process();
return promise;
}
async #process() {
while (this.#queue.length > 0) {
const { task, resolve, reject } = this.#queue.shift();
try {
const result = await task();
resolve(result);
} catch (error) {
reject(error);
}
}
}
}
Array.fromAsync (ES2024)
Creates an array from an async iterable. It is the asynchronous version of Array.from.
// Async generator
async function* fetchPages(totalPages) {
for (let page = 1; page <= totalPages; page++) {
// Simulating API call
await new Promise((r) => setTimeout(r, 100));
yield { page, items: [`item_${page}_1`, `item_${page}_2`] };
}
}
// Convert async iterable to array
const pages = await Array.fromAsync(fetchPages(3));
console.log(pages);
// [
// { page: 1, items: ["item_1_1", "item_1_2"] },
// { page: 2, items: ["item_2_1", "item_2_2"] },
// { page: 3, items: ["item_3_1", "item_3_2"] },
// ]
// Using with a mapping function
const pageNumbers = await Array.fromAsync(
fetchPages(3),
(data) => data.page
);
console.log(pageNumbers); // [1, 2, 3]
Set Methods (ES2025)
Set operation methods have been added, allowing convenient intersection, union, and difference operations.
const frontendSkills = new Set(["JavaScript", "React", "CSS", "TypeScript"]);
const backendSkills = new Set(["Node.js", "Python", "TypeScript", "SQL"]);
// Intersection — elements in both sets
const common = frontendSkills.intersection(backendSkills);
console.log([...common]); // ["TypeScript"]
// Union — all elements from both sets
const allSkills = frontendSkills.union(backendSkills);
console.log([...allSkills]);
// ["JavaScript", "React", "CSS", "TypeScript", "Node.js", "Python", "SQL"]
// Difference — elements only in the first set
const onlyFrontend = frontendSkills.difference(backendSkills);
console.log([...onlyFrontend]); // ["JavaScript", "React", "CSS"]
// Symmetric difference — elements in only one set
const exclusive = frontendSkills.symmetricDifference(backendSkills);
console.log([...exclusive]);
// ["JavaScript", "React", "CSS", "Node.js", "Python", "SQL"]
// Subset check
const coreSkills = new Set(["JavaScript", "TypeScript"]);
console.log(coreSkills.isSubsetOf(frontendSkills)); // true
console.log(frontendSkills.isSupersetOf(coreSkills)); // true
console.log(frontendSkills.isDisjointFrom(backendSkills)); // false
| Method | Description | Math symbol |
|---|---|---|
intersection | Intersection | A ∩ B |
union | Union | A ∪ B |
difference | Difference | A - B |
symmetricDifference | Symmetric difference | A △ B |
isSubsetOf | Subset check | A ⊆ B |
isSupersetOf | Superset check | A ⊇ B |
isDisjointFrom | Disjoint check | A ∩ B = ∅ |
Iterator.prototype Helpers (ES2025)
Iterators now have lazy methods like map, filter, and take. Unlike array methods, they do not create intermediate arrays, making them efficient for processing large datasets.
// Extract only the first 5 matching items from large data
function* naturalNumbers() {
let n = 1;
while (true) {
yield n++;
}
}
// Filter for even numbers from an infinite iterator, take first 5
const firstFiveEvens = naturalNumbers()
.filter((n) => n % 2 === 0) // Lazy filtering
.map((n) => n * n) // Lazy mapping
.take(5) // First 5
.toArray(); // Convert to array
console.log(firstFiveEvens); // [4, 16, 36, 64, 100]
// .drop() to skip the beginning
const result = naturalNumbers()
.filter((n) => n % 3 === 0)
.drop(2) // Skip first 2 (skip 3, 6)
.take(3) // Take next 3 (9, 12, 15)
.toArray();
console.log(result); // [9, 12, 15]
Array’s map/filter execute immediately and create intermediate arrays, while Iterator helpers use lazy evaluation — the actual computation happens when toArray() is called. This makes it safe to work with infinite sequences.
Summary
The key features added in ES2024-2025 natively support patterns frequently used in production.
- Object.groupBy: Group array elements by criteria (no lodash needed)
- Promise.withResolvers: Externalize a Promise’s resolve/reject
- Array.fromAsync: Convert async iterables to arrays
- Set methods: Set operations like intersection, union, and difference
- Iterator helpers: Lazy evaluation methods like map, filter, and take
- Browser compatibility: Most major browsers support these features, but
Iteratorhelpers may need polyfills