The JavaScript language has been evolving at a remarkable pace in recent years. The ECMAScript standard ships a new version annually, and because of this the language keeps growing and improving. Many developers still continue writing in older patterns, even though the language itself has long offered far more convenient and cleaner solutions. In this article we will walk through the most important and practical features adopted roughly between ES2022 and ES2026, examining each one with a concrete code example.
Top-level await โ simplifying waiting in modules
Previously the await keyword could only be used inside an async function. This led to awkward constructs whenever you needed to load data at the top level of a module. Introduced in ES2022, top-level await lets you await directly in an ES module itself. This is especially handy when loading dynamic configuration or establishing a database connection at the moment the module loads.
// Old approach โ wrapped async function
(async () => {
const config = await fetch('/config.json').then(r => r.json());
init(config);
})();
// New approach โ top-level await (ES2022)
const config = await fetch('/config.json').then(r => r.json());
init(config);
This feature works only in ES modules, and other modules that import this one will wait for it to finish loading. It is supported in Node.js 14.8 and above, as well as in every modern browser.
Object.groupBy โ grouping an array
Grouping data by some characteristic comes up constantly in practice. In the past you had to write this manually with reduce, and such code was often hard to read. The Object.groupBy method introduced in ES2024 solves this in a single line. It accepts a callback invoked for each element and groups the elements by the key that callback returns.
const products = [
{ name: 'Apple', type: 'fruit' },
{ name: 'Carrot', type: 'vegetable' },
{ name: 'Banana', type: 'fruit' }
];
// New approach (ES2024)
const grouped = Object.groupBy(products, p => p.type);
// { fruit: [...], vegetable: [...] }
If the grouping keys are objects or complex values, you use Map.groupBy instead, since a plain object key can only be a string or a symbol. These methods are available starting from Node.js 21 and in the latest browser versions.
Promise.withResolvers โ simplifying promise control
Sometimes you need to create a promise but call its resolve and reject functions elsewhere. Previously you had to declare external variables and assign them inside the promise constructor. The Promise.withResolvers method from ES2024 officially standardizes this pattern and cleans up the code.
// Old approach
let resolve, reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
// New approach (ES2024)
const { promise, resolve, reject } = Promise.withResolvers();
// Now resolve/reject can be called anywhere
button.onclick = () => resolve('clicked');
This is particularly useful when working with event-driven code, queues, and WebSocket connections. The method is supported in Node.js 22 and all modern browsers.
structuredClone โ deep copying
Getting a deep copy of an object was a painful topic in JavaScript for a long time. Many developers used the JSON.parse(JSON.stringify(obj)) trick, but it lost dates, Maps, Sets, and other complex types. The global structuredClone function, available in both the browser and Node.js, creates a true deep copy while preserving the correct types.
const original = {
date: new Date(),
nested: { items: new Set([1, 2, 3]) },
map: new Map([['a', 1]])
};
const copy = structuredClone(original);
// date stays a Date, Set and Map are preserved
copy.nested.items.add(4);
// original is not modified
Keep in mind that structuredClone cannot copy functions or DOM nodes โ it throws an error in those cases. The function works starting from Node.js 17 and in all modern browsers.
Immutable array methods โ toSorted, toReversed, with
The classic sort, reverse, and splice methods mutated the original array, which led to unexpected bugs in reactive libraries such as React. The new methods introduced in ES2023 return a new array without touching the original. The toSorted, toReversed, toSpliced, and with methods serve exactly this purpose.
const numbers = [3, 1, 2];
// Old approach โ original array gets clobbered
const sorted = [...numbers].sort();
// New approach (ES2023) โ original array is preserved
const sortedNew = numbers.toSorted();
const reversed = numbers.toReversed();
const replaced = numbers.with(0, 99); // [99, 1, 2]
// numbers is unchanged: [3, 1, 2]
In addition, the at method lets you grab an element from the end via a negative index, while findLast and findLastIndex let you search an array starting from the end. These make your code noticeably more precise and readable.
Temporal โ modern date and time handling
The old Date object in JavaScript had many shortcomings, and working with time zones and date arithmetic was extremely tedious. The new Temporal API fully solves these problems. It provides immutable objects, precise time-zone support, and convenient arithmetic methods. Temporal is now arriving in browsers in stages, and you can already use it today through a polyfill.
// Today's date (Temporal)
const today = Temporal.Now.plainDateISO();
// Add 30 days โ simple and exact
const future = today.add({ days: 30 });
// Difference between two dates
const diff = today.until(future);
console.log(diff.days); // 30
With Temporal, time zones are handled through explicitly defined types, so complex tasks like computing the time in two cities at once run without errors. It is one of the most anticipated additions making its way into real-world development.
Iterator helpers โ efficient stream processing
The new helper methods for iterators let you operate directly on streams without converting them into an array. This is especially memory-efficient when working with large or infinite sequences, since not all elements are loaded into memory at once. Methods like map, filter, take, and drop can now be chained on iterators.
function* naturalNumbers() {
let n = 1;
while (true) yield n++;
}
// Take only the part you need from an infinite stream
const result = naturalNumbers()
.map(n => n * n)
.filter(n => n % 2 === 0)
.take(3)
.toArray();
// [4, 16, 36]
This approach supports a functional programming style and is very handy when processing large volumes of data in stages. Iterator helpers are available in Node.js 22 and the latest browsers, significantly enriching the language's capabilities. By learning these features, you can write more modern and cleaner code.