Что нового в ECMAScript 2026
ES2026 добавляет семь функций JavaScript: Array.fromAsync, Math.sumPrecise, base64 и hex для Uint8Array, доступ к исходному тексту в JSON.parse и getOrInsert.
Ecma International утвердила спецификацию языка ECMAScript 2026 30 июня 2026 года, и она содержит ровно семь новых возможностей: Array.fromAsync, Error.isError, Math.sumPrecise, методы для работы с base64 и hex у Uint8Array, Iterator.concat, доступ к исходному тексту в JSON.parse и getOrInsert для Map и WeakMap.
Каждый год эти методы появляются незаметно, и большинство из нас узнаёт о них в тот день, когда удаляет вспомогательную функцию, которую таскало с собой годами. На этот раз, вероятно, это будет утилита для base64.
Если вы недавно искали информацию про ES2026, вы наверняка нашли списки, которые противоречат друг другу, причём в некоторые из них включены Temporal или ключевое слово using. Эти списки неверны, и данная статья объясняет почему, а затем разбирает каждую из семи возможностей, которые действительно вошли в стандарт: какую проблему она решает, какой код она заменяет и тот однострочник, который приходит на замену.
Авторитетным источником является объявление Ecma об утверждении, которое подтверждает, что ES2026 — это 17-я редакция ECMA-262, а также таблица завершённых предложений TC39, где ровно семь строк имеют ожидаемый год публикации 2026.
Ключевые выводы
- ES2026 был утверждён 30 июня 2026 года и добавляет ровно семь возможностей; Temporal и Explicit Resource Management в их число не входят.
Math.sumPrecise([1e17, 1, -1e17])возвращает1, тогда как сумма тех же значений черезreduceдаёт0.Uint8Arrayполучает встроенныеtoBase64(),toHex(),fromBase64()иfromHex(), что позволяет избавиться от распространённой зависимости.- Reviver-функция в
JSON.parseтеперь получает третий аргумент, открывающий доступ к исходному тексту, аJSON.rawJSON()решает проблему на стороне сериализации, поэтому BigInt теперь может пройти полный цикл через JSON без потерь. Map.prototype.getOrInsert(key, value)возвращает существующее значение, если ключ уже присутствует; ваше значение по умолчанию вставляется только при отсутствии ключа.
Что не попало в ES2026?
Temporal и Explicit Resource Management (using / await using) не входят в ES2026. В списке завершённых предложений TC39 у обоих указан ожидаемый год публикации 2027, наряду с Atomics.pause и Joint Iteration. Все четыре достигли stage 4 — финальной ступени процесса TC39 — уже после срока отсечения для ES2026. Публикации конца 2025 года, вышедшие до утверждения, широко предсказывали их появление в 2026-м, откуда и берутся противоречивые списки возможностей. Проверить это может кто угодно: отфильтруйте столбец Expected Publication Year в таблице завершённых предложений. Семь предложений содержат 2026, и ни одно из них не является Temporal.
Array.fromAsync
Array.fromAsync принимает асинхронный итератор и возвращает массив, обёрнутый в промис. Он также принимает синхронный итератор, значениями которого являются промисы, объект, похожий на массив, и необязательную функцию отображения — точно так же, как Array.from. До сих пор сбор значений асинхронного итератора означал цикл for await с добавлением элементов в изменяемый массив:
const arr = [];
for await (const n of asyncGen(1, 3)) {
arr.push(`page ${n}`);
}
Тот же результат теперь достигается одним выражением:
const arr = await Array.fromAsync(asyncGen(1, 3), (n) => `page ${n}`);
// [ 'page 1', 'page 2', 'page 3' ]
Error.isError
Error.isError(value) возвращает true только тогда, когда перехваченное значение является настоящим объектом Error. JavaScript позволяет выбросить что угодно, а instanceof Error начинает вводить в заблуждение, как только значения пересекают границы iframe или realm, поскольку у каждого realm есть свой собственный конструктор Error. Блок catch, условие которого опирается на instanceof Error, может незаметно пропустить ветку, отображающую полезное сообщение, и записи сессий с такими багами имеют знакомую картину: пользователь застрял на экране, который ничего не сообщает, тогда как под ним произошло реальное исключение. Error.isError устраняет этот пробел.
try {
throw new Error("session upload failed");
} catch (error) {
Error.isError(error); // true
}
try {
throw "session upload failed";
} catch (error) {
Error.isError(error); // false
}
Math.sumPrecise
Math.sumPrecise суммирует итерируемый набор чисел, не накапливая ошибку с плавающей точкой в промежуточных результатах. Цепочка reduce складывает попарно, поэтому большие и малые величины взаимно уничтожаются с плохими последствиями:
const values = [1e17, 1, -1e17];
values.reduce((a, b) => a + b, 0); // 0
Math.sumPrecise(values); // 1
Вариант с reduce полностью теряет 1. Помимо точности, это также избавляет от шаблонного редьюсера для суммирования.
Uint8Array toBase64, toHex, fromBase64 и fromHex
ES2026 добавляет в Uint8Array встроенные методы toBase64() и toHex(), а также статические Uint8Array.fromBase64() и Uint8Array.fromHex() для декодирования. Раньше преобразование двоичных данных в текст означало подключение библиотеки или написание кодирования вручную; теперь это зависимость, которую большинство кодовых баз может удалить.
const value = new Uint8Array([79, 112, 101, 110, 82, 101, 112, 108, 97, 121]);
const valueBase64 = value.toBase64(); // 'T3BlblJlcGxheQ=='
const decoded = Uint8Array.fromBase64(valueBase64);
new TextDecoder().decode(decoded); // 'OpenReplay'
Iterator.concat
Iterator.concat() последовательно объединяет итераторы в цепочку. До сих пор единственным способом сделать это был написанный вручную генератор, который проходил по каждому источнику и передавал управление через yield*:
function* combine(...iterators) {
for (const source of iterators) {
yield* source;
}
}
Теперь это один вызов, а между итераторами можно вставлять обычные массивы значений. [300] в середине примера ниже — это обычный массив, и его не нужно оборачивать в Iterator.from.
const iOne = Iterator.from([100, 200]);
const iTwo = Iterator.from([400, 500]);
const combined = Iterator.concat(iOne, [300], iTwo);
Array.from(combined); // [ 100, 200, 300, 400, 500 ]
Доступ к исходному тексту в JSON.parse
До ES2026 полный цикл преобразования JSON в JavaScript был лишён точности в обоих направлениях. Разбор литерала большого целого числа незаметно его округляет, а сериализация BigInt выбрасывает исключение:
JSON.parse("9007199254740993"); // 9007199254740992
JSON.stringify(9007199254740993n); // TypeError: Do not know how to serialize a BigInt
Предложение о доступе к исходному тексту в JSON.parse решает проблему с обеих сторон. JSON.parse теперь передаёт своей reviver-функции третий аргумент, содержащий нетронутый исходный текст source, который вы можете преобразовать самостоятельно без потери цифр. На стороне сериализации JSON.rawJSON() позволяет replacer-функции вывести значение буквально:
JSON.parse("9007199254740993", (key, value, { source }) => BigInt(source));
// 9007199254740993n
JSON.stringify(9007199254740993n, (key, value) => JSON.rawJSON(value));
// 9007199254740993
Теперь BigInt может пройти полный цикл через JSON, не теряя цифр и не вызывая исключения.
Map.prototype.getOrInsert и WeakMap.prototype.getOrInsert
Предложение Upsert добавляет getOrInsert(key, value) как в Map, так и в WeakMap, поэтому вам больше не нужно вызывать has(), а затем set(). Семантика здесь важна: getOrInsert возвращает существующее значение, если ключ уже присутствует. Вставка происходит — и ваше значение по умолчанию возвращается — только тогда, когда ключ отсутствует.
const limits = new Map();
limits.set("uploads", 25);
limits.getOrInsert("exports", 10); // 10 (inserted)
limits.getOrInsert("uploads", 100); // 25 (existing value wins)
Обратите внимание, что 100 отбрасывается, поскольку "uploads" уже был установлен. Рассматривайте второй аргумент как значение по умолчанию, а не как перезапись.
Что можно удалить прямо сейчас?
ES2026 — это релиз-уборка: каждая из семи возможностей избавляет от паттерна, который у вас, скорее всего, есть в продакшене сегодня. Зависимость-хелпер для base64, редьюсер для суммирования, защитные блоки has()-затем-set(), генератор объединения через yield* и цикл сбора for await — у всего этого теперь есть прямые встроенные замены. Доступность в рантаймах различается от движка к движку, поэтому перед отказом от полифилов проверьте справочник стандартных встроенных объектов на MDN, чтобы узнать актуальную картину поддержки каждого метода. Затем начните с самой простой победы в вашей кодовой базе — для большинства команд это замена проверок существования ключа в Map на getOrInsert.
Часто задаваемые вопросы
В чём разница между Array.fromAsync и Promise.all?
Array.fromAsync ожидает значения последовательно, одно за другим, и принимает асинхронные итерируемые объекты, синхронные итерируемые объекты, отдающие промисы, и объекты, похожие на массив. Promise.all выполняет итерируемый набор промисов параллельно и отклоняется сразу же, как только отклоняется любой из промисов. Используйте Promise.all для параллельной работы, а Array.fromAsync — когда важен порядок или когда источником является асинхронный итератор. Оба возвращают промис, который разрешается массивом.
Возвращает ли Error.isError значение true для TypeError и других подклассов Error?
Да. Error.isError возвращает true для любого настоящего подкласса Error, включая TypeError, RangeError, SyntaxError и пользовательские классы, наследующие Error, поскольку движки проверяют внутреннюю метку (brand), которую несут реальные ошибки, а не цепочку прототипов. В браузерах DOMException тоже считается реальной ошибкой. Метод возвращает false для объектов, которые лишь имитируют ошибки, например для обычных объектов со свойством message или объектов, созданных через Object.create(Error.prototype).
Может ли Uint8Array.prototype.toBase64 генерировать URL-безопасный base64?
Да. toBase64 принимает объект опций со свойством alphabet, установленным в 'base64' (значение по умолчанию, использующее + и /) или 'base64url' (использующее - и _), а также логический флаг omitPadding, который убирает завершающие символы =. Uint8Array.fromBase64 принимает ту же опцию alphabet и опцию lastChunkHandling ('loose', 'strict' или 'stop-before-partial'), которая управляет тем, как декодируется финальный фрагмент короче четырёх символов.
Как избежать вычисления «дорогого» значения по умолчанию при использовании getOrInsert?
Используйте getOrInsertComputed(key, callback), который то же предложение Upsert добавляет и в Map, и в WeakMap. Он вызывает колбэк для получения значения только в том случае, если ключ отсутствует, а затем вставляет и возвращает результат; если ключ уже существует, колбэк никогда не выполняется и возвращается существующее значение. Обычный getOrInsert всегда вычисляет свой второй аргумент до вызова, даже когда ключ уже присутствует.
Complete picture for complete understanding
Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.
Star on GitHub12k