ECMAScript 2025 (ES2025) to 16. wydanie standardu JavaScript, zatwierdzone w czerwcu 2025 roku. Dodaje m.in. metody iteratorów (map,filter,take), operacje na zbiorach (union,intersection),Promise.try,RegExp.escape, modyfikatory wyrażeń regularnych, import plików JSON jako modułów orazFloat16Array. W Node.js 22 działają z nich tylko iteratory, metodySeti import JSON.
Co roku komitet TC39 dopisuje do JavaScriptu kilka nowych funkcji, a w czerwcu Ecma International zatwierdza je jako kolejne wydanie standardu. W tym artykule przejdziemy po nowościach z ES2025 i każdą pokażemy na krótkim przykładzie, a tam, gdzie w serwisie jest artykuł o powiązanym temacie, dostaniesz do niego link. Cały kod uruchomiłem w przeglądarce Chromium 156 i w Node.js 22, a przy każdej funkcji podaję, co zwraca wynik, żebyś mógł go porównać ze swoim. Nowości z kolejnego roku opisujemy w artykule o ES2026.
Czym jest ES2025 i jakie nowości przynosi?
ECMAScript to oficjalna specyfikacja, na podstawie której działa JavaScript. Przeglądarki i środowiska takie jak Node.js wdrażają ją na własnym tempie. ES2025 to 16. edycja standardu ECMA-262, zatwierdzona w czerwcu 2025 roku. Do wydania trafiają propozycje, które w TC39 osiągnęły ostatni, czwarty etap (stage 4).
| Nowość | Do czego służy |
|---|---|
Iterator helpers (map, filter, take, drop, toArray…) | przetwarzanie iteratorów i generatorów jak tablic, ale leniwie |
Metody Set (union, intersection, difference…) | operacje na zbiorach bez ręcznych pętli |
Promise.try | uruchamia funkcję i zawsze zwraca obietnicę, także gdy funkcja rzuci wyjątek |
RegExp.escape | zamienia zwykły tekst na bezpieczny fragment wyrażenia regularnego |
Modyfikatory wyrażeń regularnych (?i:…) | włącza lub wyłącza flagi tylko dla fragmentu wzorca |
| Zduplikowane nazwy grup | ta sama nazwa grupy w różnych alternatywach wzorca |
Moduły JSON i atrybuty importu (with { type: 'json' }) | import pliku JSON jak modułu |
Float16Array, Math.f16round | liczby 16-bitowe |
Czy mogę używać nowości z ES2025 już dziś?
Zależy od środowiska. Sprawdziłem dwa:
| Nowość | Chromium 156 | Node.js 22.22.3 |
|---|---|---|
| Iterator helpers | tak | tak |
Metody Set | tak | tak |
Import JSON z with { type: 'json' } | tak | tak |
Promise.try | tak | nie |
RegExp.escape | tak | nie |
| Modyfikatory regex | tak | nie (błąd składni) |
| Zduplikowane nazwy grup | tak | nie |
Float16Array, Math.f16round | tak | nie |
Firefoksa, Safari i nowszych wersji Node.js nie sprawdzałem, więc zajrzyj do aktualnych tabel zgodności (np. MDN), zanim użyjesz którejś funkcji w projekcie. Dostępność większości z nich sprawdzisz w samym kodzie, np. typeof Promise.try === "function". Wyjątkiem są modyfikatory regex: nieobsługiwany zapis kończy się błędem składni już przy wczytaniu pliku, więc wzorzec trzeba wtedy zbudować przez new RegExp w bloku try.
Jak przetwarzać iteratory jak tablice (iterator helpers)?
Do tej pory map i filter miały tylko tablice. ES2025 dodaje je do każdego iteratora, więc możesz przetwarzać generator bez zamieniania go najpierw na tablicę. Metody działają leniwie: liczą kolejne elementy dopiero wtedy, gdy ktoś o nie poprosi. Dzięki temu da się przetwarzać nawet nieskończony generator.
function* naturals() {
let n = 1;
while (true) yield n++;
}
const squares = naturals()
.filter((n) => n % 2 === 0)
.map((n) => n * n)
.take(3)
.toArray();
console.log(squares); // [4, 16, 36]
Bez take(3) ten kod nigdy by się nie skończył, bo generator nie ma końca. Ze take iterator przerywa pracę po trzech wynikach. Leniwość widać też na prostym doświadczeniu: iterator utworzony przez .values().map(...) nie wykonuje funkcji, dopóki go nie odczytasz.
const log = [];
const it = [1, 2, 3, 4].values().map((x) => {
log.push(x);
return x * 2;
});
console.log(log); // [] (nic się jeszcze nie policzyło)
console.log(it.next().value); // 2
console.log(log); // [1] (policzył tylko pierwszy element)
Poza map, filter i take dostajesz drop (pomija początek), flatMap, reduce, some, every, find, forEach oraz toArray. Statyczna metoda Iterator.from opakowuje w iterator także zwykłe obiekty iterowalne, np. napis: Iterator.from('abc').toArray() zwraca ['a', 'b', 'c'].
Jak to się ma do tego, co znasz? Metody tablicowe tworzą po drodze nową tablicę przy każdym kroku, a iteratory przechodzą dane raz, element po elemencie. Z iteratorami działa też pętla for…of, więc for (const n of naturals().take(3)) jest równie poprawne. A wyrażenia w map i filter zapiszesz krócej jako funkcje strzałkowe.
Jak wykonać operacje na zbiorach (union, intersection, difference)?
Set przechowuje unikalne wartości, ale do ES2025 operacje na dwóch zbiorach trzeba było pisać ręcznie, zwykle przez zamianę na tablicę i filter. Teraz są gotowe metody:
const known = new Set(["js", "css", "html"]);
const needed = new Set(["css", "react"]);
console.log([...known.union(needed)]); // ["js", "css", "html", "react"]
console.log([...known.intersection(needed)]); // ["css"]
console.log([...known.difference(needed)]); // ["js", "html"]
console.log([...known.symmetricDifference(needed)]); // ["js", "html", "react"]
console.log(new Set(["css"]).isSubsetOf(known)); // true
console.log(known.isSupersetOf(new Set(["css"]))); // true
console.log(known.isDisjointFrom(new Set(["php"]))); // true
W przykładzie known to umiejętności, które już masz, a needed to wymagania z ogłoszenia. difference od razu pokazuje, czego ci brakuje, a intersection, co się pokrywa. Metody zwracają nowy zbiór i nie zmieniają oryginału (po wszystkich wywołaniach known nadal ma trzy elementy). Metody isSubsetOf, isSupersetOf i isDisjointFrom zwracają true lub false.
Wynik jest zbiorem, więc do wyświetlenia zamieniamy go na tablicę przez operator rozproszenia ([...zbiór]). Jeśli dopiero poznajesz kolekcje w JavaScripcie, zacznij od tablic w JavaScript.
Jak obsłużyć funkcję, która może rzucić wyjątek lub zwrócić obietnicę (Promise.try)?
Wyobraź sobie funkcję, która czasem rzuca wyjątek od razu (np. gdy brakuje argumentu), a czasem zwraca obietnicę. Jeśli zawołasz ją zwyczajnie, wyjątek wymknie się spod .catch. Promise.try uruchamia funkcję i zawsze zwraca obietnicę: wyjątek zamienia na odrzuconą obietnicę, a zwróconą wartość na spełnioną.
function getUser(id) {
if (!id) throw new Error("brak id");
return Promise.resolve({ id });
}
// Bez Promise.try: wyjątek leci poza .catch
try {
getUser(0).catch(() => {});
} catch (e) {
console.log("poza .catch:", e.message); // poza .catch: brak id
}
// Z Promise.try: jeden .catch obsługuje oba przypadki
const a = await Promise.try(getUser, 0).catch((e) => "catch: " + e.message);
const b = await Promise.try(getUser, 7);
console.log(a); // catch: brak id
console.log(b); // { id: 7 }
Ważna różnica względem Promise.resolve().then(fn): Promise.try wykonuje funkcję od razu, a then dopiero w następnym kroku pętli zdarzeń. Sprawdziliśmy kolejność wykonania:
const log = [];
log.push("start");
Promise.try(() => log.push("wewnątrz Promise.try"));
Promise.resolve().then(() => log.push("wewnątrz then"));
log.push("koniec");
await new Promise((r) => setTimeout(r));
console.log(log.join(" | "));
// start | wewnątrz Promise.try | koniec | wewnątrz then
Kod z Promise.try wykonał się przed koniec, a kod z then po nim. To przydatne, gdy chcesz mieć jednolitą obsługę błędów, ale nie opóźniać pracy funkcji. Więcej o tym, jak działa kod asynchroniczny, znajdziesz w artykułach o tym, jak uzależnić wykonanie kodu od odpowiedzi serwera, i o testowaniu kodu asynchronicznego.
Jak bezpiecznie wstawić tekst użytkownika do wyrażenia regularnego (RegExp.escape)?
Gdy budujesz wyrażenie regularne z tekstu wpisanego przez użytkownika (np. wyszukiwarka), znaki specjalne takie jak (, + czy ? zmieniają znaczenie wzorca. Dotąd trzeba było pisać własną funkcję z replace. RegExp.escape robi to za ciebie:
const input = "cena (PLN) [brutto]? a+b.c";
console.log(new RegExp(input).test(input));
// false (nawiasy, plus i kropka zostały potraktowane jako składnia wzorca)
const safe = RegExp.escape(input);
console.log(safe);
// \x63ena\x20\(PLN\)\x20\[brutto\]\?\x20a\+b\.c
console.log(new RegExp(safe).test(input)); // true
Zwróć uwagę na dwie rzeczy w wyniku. Po pierwsze, znaki specjalne dostały ukośnik, więc wzorzec dopasowuje dosłowny tekst. Po drugie, pierwsza litera (c) i spacje zostały zapisane jako \x63 i \x20. To celowe: dzięki temu wynik jest bezpieczny także wtedy, gdy kleisz go w środek większego wzorca (np. po \1, albo gdy tekst zaczyna się od cyfry i mógłby się „skleić” z poprzednim fragmentem).
To szczególnie ważne, gdy tekst pochodzi z formularza. Wpisanie do wyszukiwarki samego znaku ( bez zabezpieczenia kończy się błędem SyntaxError w new RegExp. Szerzej o bezpiecznym traktowaniu danych od użytkownika piszemy w artykule o tym, czym jest XSS i jak się przed nim bronić, a o sprawdzaniu pól w formularzu w poradniku o walidacji formularza w JavaScript.
Jak zmienić wielkość liter tylko w części wzorca (modyfikatory regex)?
Flaga i w wyrażeniu regularnym dotyczyła całego wzorca. W ES2025 możesz włączyć lub wyłączyć ją tylko dla fragmentu, zapisem (?i:...) oraz (?-i:...).
// "kod" bez względu na wielkość liter, ale cyfry zawsze po myślniku
const re = /^(?i:kod)-\d+$/;
console.log(re.test("KOD-12")); // true
console.log(re.test("Kod-12")); // true
console.log(re.test("kod-ab")); // false
// Wyłączenie flagi i dla jednej litery
console.log(/(?-i:a)b/i.test("Ab")); // false (a musi być małe)
console.log(/(?-i:a)b/i.test("aB")); // true (b może być dowolne)
Wcześniej do tego samego trzeba było pisać [kK][oO][dD] albo dzielić wzorzec na dwa. Uwaga na zgodność: w Node.js 22 sam zapis (?i:...) kończy się SyntaxError: Invalid regular expression: Invalid group już przy wczytaniu pliku, więc nie sprawdzisz go przez typeof. W starszych środowiskach buduj taki wzorzec w try.
Jak użyć tej samej nazwy grupy w kilku alternatywach (zduplikowane nazwy grup)?
Grupy nazwane ((?<nazwa>...)) pozwalają odczytać dopasowanie po nazwie. Do ES2025 nazwa musiała być unikalna w całym wzorcu, więc przy dwóch formatach daty trzeba było tworzyć dwie grupy i sprawdzać, która coś zwróciła. Teraz ta sama nazwa może wystąpić w różnych alternatywach (|):
const re = /(?<date>\d{4}-\d{2}-\d{2})|(?<date>\d{2}\.\d{2}\.\d{4})/;
console.log("2026-11-05".match(re).groups.date); // 2026-11-05
console.log("05.11.2026".match(re).groups.date); // 05.11.2026
Niezależnie od formatu wynik leży w jednym miejscu, groups.date. Możesz go wyciągnąć destrukturyzacją: const { groups: { date } } = "05.11.2026".match(re);. Ograniczenie: ta sama nazwa może być użyta tylko w alternatywach, które nie mogą dopasować się jednocześnie. Dwie takie same nazwy obok siebie, bez |, nadal są błędem.
Jak zaimportować plik JSON jak moduł (moduły JSON i atrybuty importu)?
Zamiast pobierać plik JSON przez fetch i ręcznie go parsować, możesz zaimportować go jak moduł. Potrzebny jest do tego atrybut importu with { type: 'json' }, który mówi środowisku, że plik ma być traktowany jako JSON, a nie jako kod.
// config.json: { "name": "devmentor", "level": "junior" }
import config from "./config.json" with { type: "json" };
console.log(config.name); // devmentor
console.log(config.level); // junior
Importowany plik jest wczytywany raz i działa jak zwykły obiekt (dostajesz go jako domyślny eksport). Bez atrybutu przeglądarka odrzuca import: w teście w Chromium dynamiczny import('./config.json') zakończył się błędem Failed to fetch dynamically imported module. Sam atrybut jest bezpieczeństwem: serwer nie może podstawić pod ten adres kodu wykonywalnego zamiast danych.
Do czego to się przydaje? Do konfiguracji, tłumaczeń i danych startowych, które i tak chcesz mieć w paczce aplikacji. Dane, które zmieniają się w czasie działania, nadal pobierasz z serwera, np. tak jak w artykule o lokalnym API z json-server. To, jak narzędzia pakują takie importy do jednego pliku, opisujemy w porównaniu Webpacka i Vite, a samo działanie na wczytanym obiekcie omawiamy w artykule obiekty w JavaScript.
Czym jest Float16Array?
ES2025 dodaje liczby 16-bitowe: Float16Array, odczyt i zapis DataView (getFloat16, setFloat16) oraz funkcję Math.f16round. To format znany z grafiki i uczenia maszynowego, gdzie liczy się oszczędność pamięci kosztem dokładności.
const f = new Float16Array([1.337, 70000, 0.1]);
console.log([...f]);
// [1.3369140625, Infinity, 0.0999755859375]
console.log(Math.f16round(1.337)); // 1.3369140625
console.log(Math.fround(1.337)); // 1.3370000123977661 (32 bity)
console.log(f.BYTES_PER_ELEMENT); // 2 (bajty na liczbę)
Zwróć uwagę na wynik: 1.337 po zapisaniu w 16 bitach to 1.3369140625, a 70000 staje się Infinity, bo największa liczba w tym formacie to 65504. Na co dzień w aplikacjach webowych będziesz używać tego rzadko, ale warto wiedzieć, że typ istnieje, jeśli trafisz na przetwarzanie obrazów lub modele AI w przeglądarce.
Co jeszcze weszło do ES2025?
Dziesiąta propozycja z listy dotyczy ponownej deklaracji zmiennych var utworzonych przez eval w zasięgu globalnym. To poprawka krawędziowa, którą zobaczysz w codziennej pracy bardzo rzadko, więc jej nie omawiamy ani nie testowaliśmy.
Co dalej?
Najlepszym sposobem na zapamiętanie nowości jest użycie ich w małym projekcie: zamień ręczne pętle na metody Set, a własną funkcję do ucieczki znaków w wyrażeniach regularnych na RegExp.escape, jeśli twoje środowisko ją obsługuje. Pamiętaj tylko, żeby przed użyciem sprawdzić, w jakich przeglądarkach i wersjach Node.js ma działać aplikacja. Nowości z kolejnego wydania znajdziesz w artykule o ES2026.
Jeśli chcesz uporządkować cały nowoczesny JavaScript pod okiem mentora, zobacz nasz kurs Zaawansowany JS.


