JavaScript receives a new specification every year, but not every release changes the way we write everyday application code.
ECMAScript 2026 is different.
The 17th edition of the ECMAScript specification was approved by Ecma International on June 30, 2026. It introduces several small but highly practical APIs that replace patterns many of us have been writing manually for years.
No new framework is required. No architectural rewrite is necessary. These are language-level improvements for frontend applications, Node.js services, libraries, APIs, data pipelines, and developer tools.
In this article, we will explore seven additions:
Array.fromAsync()Map.prototype.getOrInsert()Iterator.concat()Error.isError()Math.sumPrecise()- Native Base64 and hex support for
Uint8Array - JSON source text access and raw JSON
Let us look at the problems they solve and where they can be useful in real projects.
Before using a new API in production, check support in your target browsers and JavaScript runtimes. Feature detection, progressive enhancement, or a compatible fallback may still be appropriate.
1. Convert async data into an array with Array.fromAsync()
JavaScript has supported Array.from() for synchronous iterables for years.
const values = Array.from(new Set([1, 2, 2, 3]));
console.log(values);
// [1, 2, 3]
However, Array.from() cannot consume an asynchronous iterable.
Until now, collecting values from an async generator usually required a manual loop:
async function* loadPages() {
yield await fetchPage(1);
yield await fetchPage(2);
yield await fetchPage(3);
}
const pages = [];
for await (const page of loadPages()) {
pages.push(page);
}
With ECMAScript 2026, we can express the same intention more directly:
const pages = await Array.fromAsync(loadPages());
Array.fromAsync() can also apply a mapping function while collecting the values:
const titles = await Array.fromAsync(
loadPages(),
page => page.title
);
A simplified full-stack example could collect results from a paginated API:
async function* fetchAllUsers() {
let page = 1;
while (true) {
const response = await fetch(`/api/users?page=${page}`);
const result = await response.json();
for (const user of result.users) {
yield user;
}
if (!result.nextPage) {
return;
}
page = result.nextPage;
}
}
const activeUsers = await Array.fromAsync(
fetchAllUsers(),
user => ({
id: user.id,
name: user.name,
active: user.lastSeenAt !== null
})
);
Why it matters
This is useful when working with:
- paginated API responses,
- database cursors,
- asynchronous generators,
- streamed records,
- file-processing pipelines,
- values that resolve asynchronously.
The main benefit is readability. The code clearly says: consume this asynchronous source and return an array.
One important consideration
An array keeps every collected item in memory. If the source can produce a very large or unlimited number of values, process items incrementally with for await...of instead.
2. Initialize Map values with getOrInsert()
A common Map pattern is to check whether a value exists, create it if necessary, and then retrieve it.
For example, grouping products by category often looks like this:
const productsByCategory = new Map();
for (const product of products) {
if (!productsByCategory.has(product.category)) {
productsByCategory.set(product.category, []);
}
productsByCategory.get(product.category).push(product);
}
ECMAScript 2026 adds Map.prototype.getOrInsert():
const productsByCategory = new Map();
for (const product of products) {
productsByCategory
.getOrInsert(product.category, [])
.push(product);
}
If the key exists, the method returns the current value. If it does not exist, the provided default is inserted and returned.
There is also getOrInsertComputed(), which calculates the value only when a key is missing:
const userCache = new Map();
function getUserProfile(userId) {
return userCache.getOrInsertComputed(
userId,
() => createUserProfile(userId)
);
}
This is especially useful when creating the default value is expensive.
const reportCache = new Map();
function getReport(projectId) {
return reportCache.getOrInsertComputed(projectId, () => ({
projectId,
createdAt: new Date(),
rows: buildInitialReportRows(projectId)
}));
}
Equivalent methods are also available for WeakMap.
Why it matters
These methods make several common patterns more concise:
- grouping API results,
- memoization,
- per-request caches,
- dependency containers,
- graph construction,
- counters and indexes,
- metadata associated with objects.
They also communicate intent better than separate has(), set(), and get() calls.
3. Combine lazy sequences with Iterator.concat()
Suppose an application receives items from multiple sources:
function* localArticles() {
yield { id: 1, source: "local" };
yield { id: 2, source: "local" };
}
function* partnerArticles() {
yield { id: 3, source: "partner" };
yield { id: 4, source: "partner" };
}
Previously, we might have created an array:
const articles = [
...localArticles(),
...partnerArticles()
];
This immediately consumes both iterators and stores all their values in memory.
ECMAScript 2026 provides Iterator.concat():
const articles = Iterator.concat(
localArticles(),
partnerArticles()
);
for (const article of articles) {
console.log(article);
}
The combined iterator remains lazy. Values are requested only when the consumer needs them.
It can also combine arrays and other synchronous iterables:
const navigationItems = Iterator.concat(
["Home", "Articles"],
getProjectLinks(),
["Settings"]
);
Why laziness is useful
Lazy iteration can help when:
- inputs are large,
- producing an item requires computation,
- the consumer may stop early,
- allocating an intermediate array is unnecessary.
For example:
const allCandidates = Iterator.concat(
getPrimaryCandidates(),
getFallbackCandidates(),
getArchivedCandidates()
);
for (const candidate of allCandidates) {
if (candidate.matches(query)) {
console.log("Found:", candidate);
break;
}
}
The later sources may never be evaluated if an earlier match is found.
4. Detect genuine Error objects with Error.isError()
Most JavaScript developers use instanceof to determine whether a caught value is an error:
try {
await saveDocument();
} catch (value) {
if (value instanceof Error) {
console.error(value.message);
}
}
This works in many applications, but it can be unreliable when the error comes from a different JavaScript realm, such as:
- an iframe,
- another browser window,
- certain VM environments,
- isolated execution contexts.
Each realm can have a different Error constructor. An error created elsewhere may therefore fail the local instanceof Error check.
ECMAScript 2026 introduces Error.isError():
try {
await saveDocument();
} catch (value) {
if (Error.isError(value)) {
console.error(value.message);
} else {
console.error("A non-Error value was thrown:", value);
}
}
A reusable normalization helper becomes straightforward:
function normalizeError(value) {
if (Error.isError(value)) {
return value;
}
if (typeof value === "string") {
return new Error(value);
}
return new Error("An unknown error occurred", {
cause: value
});
}
Why it matters
This API is particularly useful for:
- component libraries,
- iframe-based applications,
- plugin systems,
- test runners,
- browser automation,
- sandboxed code,
- error-monitoring SDKs.
It gives the language a direct and explicit way to answer a surprisingly difficult question: is this value actually an Error object?
5. Sum floating-point values more accurately with Math.sumPrecise()
Floating-point arithmetic can lose precision, especially when values with very different magnitudes are added sequentially.
Consider this calculation:
const values = [1e16, 1, -1e16];
const total = values.reduce(
(sum, value) => sum + value,
0
);
console.log(total);
// Commonly 0, although the mathematical result is 1
The small 1 can disappear during intermediate floating-point operations.
ECMAScript 2026 adds Math.sumPrecise():
const total = Math.sumPrecise([
1e16,
1,
-1e16
]);
console.log(total);
// 1
It accepts an iterable, so it can also work with data transformations:
const completedOrderValues = orders
.filter(order => order.status === "completed")
.map(order => order.total);
const revenue = Math.sumPrecise(completedOrderValues);
Or with a generator:
function* validMeasurements(rows) {
for (const row of rows) {
if (Number.isFinite(row.measurement)) {
yield row.measurement;
}
}
}
const total = Math.sumPrecise(
validMeasurements(dataset)
);
Important: this is not decimal arithmetic
Math.sumPrecise() reduces floating-point accumulation error, but JavaScript numbers are still IEEE 754 floating-point values.
For money, the safest approach is usually to store minor units as integers:
const pricesInCents = [1999, 2500, 349];
const totalInCents = pricesInCents.reduce(
(sum, value) => sum + value,
0
);
Use Math.sumPrecise() when you intentionally work with floating-point measurements, statistics, coordinates, scientific data, or mixed-magnitude values.
6. Encode and decode binary data without custom Base64 helpers
Web applications frequently need to convert binary data to Base64 or hexadecimal strings.
Common use cases include:
- API payloads,
- cryptographic data,
- file chunks,
- content hashes,
- authentication challenges,
- browser storage.
Historically, developers often combined btoa(), atob(), loops, buffers, or third-party helpers. Those approaches could be awkward, environment-specific, or unsafe for arbitrary binary data.
ECMAScript 2026 adds dedicated methods to Uint8Array.
Base64
const bytes = new Uint8Array([
72, 101, 108, 108, 111
]);
const encoded = bytes.toBase64();
console.log(encoded);
// SGVsbG8=
Decode it again:
const decoded = Uint8Array.fromBase64(
"SGVsbG8="
);
console.log(decoded);
// Uint8Array(5) [72, 101, 108, 108, 111]
Hexadecimal
const token = new Uint8Array([
15, 160, 255
]);
console.log(token.toHex());
// 0fa0ff
And in the opposite direction:
const bytes = Uint8Array.fromHex(
"0fa0ff"
);
A digest helper can now be written cleanly:
async function createSha256Hex(input) {
const source = new TextEncoder().encode(input);
const digest = await crypto.subtle.digest(
"SHA-256",
source
);
return new Uint8Array(digest).toHex();
}
Why it matters
These methods create a clearer, standardized path for binary conversion across JavaScript environments.
The code is easier to review because developers no longer need to recognize a custom chain of character conversions as βbinary data to Base64.β
7. Preserve important JSON source information
JSON numbers are parsed into JavaScript Number values. This can cause information loss when the source contains an integer larger than JavaScript can represent safely.
const payload =
'{"transactionId":123456789012345678901234567890}';
const result = JSON.parse(payload);
console.log(result.transactionId);
// The original integer may already have lost precision
ECMAScript 2026 extends the JSON.parse() reviver with access to the original source text.
const payload =
'{"transactionId":123456789012345678901234567890}';
const result = JSON.parse(
payload,
(key, value, context) => {
if (key === "transactionId") {
return BigInt(context.source);
}
return value;
}
);
console.log(result.transactionId);
// 123456789012345678901234567890n
The key detail is context.source. It shows the matched JSON source before the already-parsed Number value becomes the only available representation.
This is useful for:
- database identifiers,
- blockchain values,
- high-precision API data,
- large counters,
- scientific datasets,
- interoperability with systems using 64-bit integers.
ECMAScript 2026 also includes JSON.rawJSON(), which lets code provide validated raw JSON for primitive values during serialization.
const exactId = JSON.rawJSON(
"123456789012345678901234567890"
);
const payload = JSON.stringify({
transactionId: exactId
});
console.log(payload);
// {"transactionId":123456789012345678901234567890}
This offers more control over output that cannot be represented exactly as a regular JavaScript Number.
Use it carefully
Raw JSON is a precision and interoperability tool, not a general string-concatenation shortcut.
Only use validated values and keep the expected schema explicit.
A quick migration checklist
You do not need to refactor an entire codebase to benefit from ES2026.
Look for these existing patterns:
Manual async collection
const results = [];
for await (const item of source) {
results.push(item);
}
Consider:
const results = await Array.fromAsync(source);
Repeated Map initialization
if (!map.has(key)) {
map.set(key, createValue());
}
const value = map.get(key);
Consider:
const value = map.getOrInsertComputed(
key,
createValue
);
Array spreads used only to join iterables
const combined = [
...sourceA,
...sourceB
];
Consider a lazy sequence:
const combined = Iterator.concat(
sourceA,
sourceB
);
Cross-context error checks
value instanceof Error
Consider:
Error.isError(value)
Custom binary conversion helpers
customBytesToHex(bytes)
Consider:
bytes.toHex()
Large integers parsed from JSON
If an API returns integer values outside the safe Number range, consider parsing the original source into BigInt or a decimal representation.
How to use these features safely today
A feature being part of the ECMAScript standard does not guarantee that every browser, embedded webview, server runtime, or build target already supports it.
A simple feature check can help:
const supportsFromAsync =
typeof Array.fromAsync === "function";
const supportsPreciseSum =
typeof Math.sumPrecise === "function";
const supportsErrorCheck =
typeof Error.isError === "function";
const supportsHex =
typeof Uint8Array.prototype.toHex === "function";
For a production project:
- Check your browser and runtime support matrix.
- Test using the same versions as your deployment environment.
- Add a fallback or polyfill where appropriate.
- Avoid assuming that transpilation automatically provides new runtime APIs.
- Upgrade dependencies and CI images deliberately.
Transpilers can transform syntax, but methods such as Math.sumPrecise() and Array.fromAsync() require runtime support or an implementation supplied by your application.
Final thoughts
ECMAScript 2026 is a good example of language evolution focused on practical improvements rather than dramatic syntax changes.
Its additions address familiar problems:
- collecting asynchronous data,
- initializing cached values,
- combining lazy sequences,
- identifying errors reliably,
- reducing summation error,
- converting binary data,
- preserving JSON precision.
None of these features will replace your framework or redesign your architecture. That is exactly why they are valuable: they make ordinary JavaScript code shorter, clearer, and more expressive.
My first candidates for immediate adoption are:
-
Map.prototype.getOrInsertComputed()for caches and grouping, -
Array.fromAsync()for finite asynchronous sources, - the new
Uint8Arrayencoding methods for API and crypto work.
Which ECMAScript 2026 feature are you most likely to use first?
Sources and further reading
- ECMAScript 2026 Language Specification
- ECMA-262 β Ecma International
- Whatβs new in ECMAScript 2026
- ECMAScript 2026 specification approved
Connect with Me
If you found this guide helpful, let's connect and discuss modern development workflows!
- π» GitHub: johnnylemonny
- βοΈ DEV.to: johnnylemonny
This article was created with the help of AI
Top comments (0)