Sorting a list without changing the original state, grouping results by category, and processing data that arrives asynchronously: these are everyday tasks, but for years each called for its own pattern. Between ES2022 and ES2026, JavaScript added native methods for several of them. Let’s follow the journey with a small product catalog, from its model to an asynchronous transformation.
JavaScript is the language; ECMAScript defines its core and its standard. MDN explains that editions are published each year and that engines incorporate new features on their own schedules. So the fact that a feature appears in an edition does not guarantee that it is available in every browser or Node.js version.
Set up the exercise
Save each example in a file with the .mjs extension and run it with Node.js. The blocks are independent: each contains its own data and can be tested separately. Since support depends on the engine, check that the specific APIs in each exercise exist in the environment where you plan to run the code; simply using a module file is not enough.
node ejercicio.mjs
Our thread will be a small catalog. We’ll start with two ES2022 changes that help represent data and query objects more precisely.
From encapsulating state to sorting without mutation
1. ES2022: protect the model’s data
Private fields using # are not just a naming convention: access from outside the class is not allowed. In this example, the inventory manages its own quantity and exposes an operation to change it. Object.hasOwn(), also from ES2022, checks whether a property belongs directly to an object.
class Producto {
#stock;
constructor(nombre, precio, stock) {
this.nombre = nombre;
this.precio = precio;
this.#stock = stock;
}
ajustarStock(cantidad) {
this.#stock += cantidad;
}
disponible() {
return this.#stock > 0;
}
}
const producto = new Producto("Taza", 12, 4);
producto.ajustarStock(-1);
console.log(producto.disponible()); // true
console.log(Object.hasOwn(producto, "nombre")); // true
The class keeps the stock behind a small interface: to find out whether any product is left, the code does not need to read the field directly. The Object.hasOwn() check answers a different question: whether nombre is defined on that instance, rather than inherited from its prototype. If you try to access producto.#stock outside the class, the program will not compile.
2. ES2023: sort a copy
sort() modifies the array it receives. In an interface that preserves the previous state, this side effect may be undesirable. toSorted() returns another array and leaves the original list unchanged.
const productos = [
{ nombre: "Taza", precio: 12 },
{ nombre: "Cuaderno", precio: 8 },
{ nombre: "Lámpara", precio: 25 }
];
const porPrecio = productos.toSorted(
(a, b) => a.precio - b.precio
);
console.log(porPrecio.map(producto => producto.nombre));
// ["Cuaderno", "Taza", "Lámpara"]
console.log(productos.map(producto => producto.nombre));
// ["Taza", "Cuaderno", "Lámpara"]
The numeric comparator makes the sorting criterion explicit; without it, sort() sorts based on conversion to text. The same ES2023 family includes toReversed(), toSpliced(), and with(): operations that create a new result instead of changing the original array. The useful idea is not to always avoid mutation, but to choose it consciously.
Grouping and transforming collections
3. ES2024: turn a list into groups
Suppose an API response has already been converted into product objects and we want to display them by category. Object.groupBy() takes the collection and a function that returns the group key.
const productos = [
{ nombre: "Manzana", categoria: "fruta" },
{ nombre: "Pera", categoria: "fruta" },
{ nombre: "Zanahoria", categoria: "verdura" }
];
const porCategoria = Object.groupBy(
productos,
producto => producto.categoria
);
console.log(porCategoria.fruta.map(producto => producto.nombre));
// ["Manzana", "Pera"]
console.log(porCategoria.verdura[0].nombre);
// "Zanahoria"
The result brings together elements in arrays according to the key returned by the function. For these categories, which are strings, an object is a convenient output. ES2024 also introduced Map.groupBy(), which is useful when you want to preserve keys that are not strings, such as boolean values or objects. In both cases, grouping does not remove elements: it classifies those already in the input.
4. ES2025: chain iterator operations
Iterator Helpers let you filter and transform elements before materializing the result. This example keeps products priced below 20, converts each one into a label, and limits the output to two elements.
const productos = [
{ nombre: "Taza", precio: 12 },
{ nombre: "Cuaderno", precio: 8 },
{ nombre: "Lámpara", precio: 25 },
{ nombre: "Bolígrafo", precio: 3 }
];
const etiquetas = Iterator.from(productos)
.filter(producto => producto.precio < 20)
.map(producto => `${producto.nombre}: ${producto.precio} €`)
.take(2)
.toArray();
console.log(etiquetas);
// ["Taza: 12 €", "Cuaderno: 8 €"]
Iterator.from() adapts the collection to the sequence of operations; filter() and map() describe the steps, and take(2) limits how many results are consumed. toArray() is where you get an array. Unlike chaining array methods, the helpers work with iterators and can defer executing transformations until the values are consumed. If the environment does not offer Iterator Helpers, this block will fail even if the other examples work.
5. ES2026: collect values from an asynchronous source
An asynchronous source is consumed differently from a regular array. Array.fromAsync() lets you collect its values into a collection. The generator in this example simulates a sequence that produces three amounts.
async function* obtenerPrecios() {
yield 12;
yield 8;
yield 25;
}
const precios = await Array.fromAsync(obtenerPrecios());
const total = precios.reduce((suma, precio) => suma + precio, 0);
console.log(precios); // [12, 8, 25]
console.log(total); // 45
The await waits for the conversion to complete; that’s why this example uses an .mjs file, where top-level code can include await. The conversion collects all values in memory: it is suitable when you need the complete array, not a reason to materialize a potentially large stream without thinking. The new feature makes conversion easier, but it does not eliminate the cost of storing the result.
Common mistakes and use in projects
The first pitfall is assuming that the year of the standard corresponds to a specific version of the environment. ECMAScript is standardized, and engines adopt features at their own pace. Before deploying these examples, check compatibility in the browsers or Node.js versions your application needs to support. MDN also distinguishes the language core from browser APIs: a function such as fetch() belongs to platform APIs; it does not make an ECMAScript feature universal.
The second is confusing a copy with a state update. toSorted() returns a new array; if the program needs to use it, save and use that result. The third is forgetting that Object.groupBy() creates groups based on the value returned by the function: a misspelled category also creates a separate group. With iterators, remember that toArray() consumes the sequence and creates the final collection.
These tools do not require you to rewrite an application or make a program modern by themselves. Their value lies in replacing helper patterns when the operation you need matches the API: sorting without touching the original, classifying a list, or chaining transformations. For a small collection, a loop can still be perfectly clear; for a shared codebase, it matters that the team understands the behavior and support for the chosen method.
The changes of recent years are noticeable in everyday work: more collection operations are available directly in the language. Test the blocks in your target engine before incorporating them; in particular, confirm support for Iterator.from() and Array.fromAsync().
Sources: TecnoPS, Ecma International / TC39, MDN Web Docs, V8