Skip to main content

ECMAScript 2026: Every New JavaScript Feature

·PkgPulse Team

Every year the TC39 committee finalizes a new ECMAScript specification, and developers get to stop polyfilling things they've been working around for years. ES2026 (ECMAScript 2026) is no exception — it brings iterator helpers that make lazy data transformation elegant, native float16 support for ML workloads, Promise.try for cleaner async error handling, safe regex escaping, and several smaller quality-of-life improvements.

This is every confirmed ES2026 feature, with the code examples that matter.

TL;DR

ES2026's headline features are Iterator Helpers (.map(), .filter(), .take() on any iterator — lazy by default), Float16Array (16-bit floating point for GPU and ML workloads), Promise.try() (wraps sync-or-async functions uniformly), RegExp.escape() (finally safe dynamic regex construction), and — finally — Temporal (the Date replacement that reached Stage 4 at the March 2026 TC39 meeting). Most core features are already shipping in Chrome 122+, Firefox 131+, and Node.js 22+.

Key Takeaways

  • Iterator Helpers.map(), .filter(), .take(), .drop(), .flatMap(), .reduce(), .forEach(), .some(), .every(), .find() on any iterator — lazy evaluation, no intermediate arrays
  • Float16Array — new TypedArray for 16-bit floats; critical for WebGPU and ML inference
  • Promise.try() — wraps sync-or-async callbacks uniformly, eliminating try/catch boilerplate at async boundaries
  • RegExp.escape() — static method to escape literal strings for use in regex patterns
  • Error.isError() — reliable cross-realm Error detection
  • Import attributes stabilizedimport data from './data.json' with { type: 'json' } syntax finalized
  • Math.f16round() — rounds to nearest float16, companion to Float16Array
  • Temporal — the modern Date replacement reaches Stage 4 at March 2026 TC39; immutable, time-zone-aware, correct arithmetic
  • using / await using (Explicit Resource Management) — deterministic scope-based cleanup with Symbol.dispose
  • Uint8Array.toBase64() / Uint8Array.fromBase64() — native binary/Base64 encoding without atob/btoa

At a Glance

FeatureStageChromeFirefoxNode.jsKey Use Case
Iterator Helpers✅ ES2026120+130+22+Lazy data pipelines
Float16Array✅ ES2026120+129+22+WebGPU, ML workloads
Promise.try()✅ ES2026128+134+22+Async boundary uniformity
RegExp.escape()✅ ES2026136+134+23+Safe dynamic regex
Error.isError()✅ ES2026134+139+24+Cross-realm error checks
Import attributes✅ ES2026123+135+20.10+JSON/CSS module imports
Math.f16round()✅ ES2026120+129+22+Float16 rounding
Temporal✅ ES2026144+139+24+ (flag)Modern Date replacement
using / await using✅ ES2026134+22+Resource cleanup
Array.fromAsync()✅ ES2026121+115+22+Async iterable collection
Map.getOrInsert✅ ES2026144+139+24+Upsert pattern
Math.sumPrecise()✅ ES2026137+136+24+Float precision summation

Iterator Helpers

The biggest DX win in ES2026. Before this, iterating lazily over generators or custom iterables required manual for...of loops or converting to arrays first. Iterator Helpers add standard functional methods directly to the iterator protocol — and they're lazy, meaning values are computed on demand rather than materializing intermediate arrays.

// Before ES2026: convert to array first (eager, allocates memory)
const results = [...someIterator]
  .filter(x => x > 10)
  .map(x => x * 2)
  .slice(0, 5)

// ES2026: lazy pipeline, no intermediate arrays
const results = someIterator
  .filter(x => x > 10)
  .map(x => x * 2)
  .take(5)
  .toArray()

Lazy Evaluation in Practice

The key difference from array methods: nothing runs until you consume the iterator.

function* generateNumbers() {
  let n = 0
  while (true) {
    yield n++
  }
}

// Without Iterator Helpers: can't use .filter().map() on infinite generators
// With Iterator Helpers: lazy — only processes what you consume
const firstFiveEvenSquares = generateNumbers()
  .filter(n => n % 2 === 0)
  .map(n => n * n)
  .take(5)
  .toArray()
// → [0, 4, 16, 36, 64]
// Only computed 10 values from the generator (0-9), not infinity

Full Iterator Helpers API

const iter = [1, 2, 3, 4, 5].values() // Array iterator implements the protocol

// .map(fn) — transform each value
iter.map(x => x * 2).toArray()           // [2, 4, 6, 8, 10]

// .filter(fn) — keep matching values
iter.filter(x => x % 2 === 0).toArray()  // [2, 4]

// .take(n) — first n values
iter.take(3).toArray()                    // [1, 2, 3]

// .drop(n) — skip first n values
iter.drop(2).toArray()                    // [3, 4, 5]

// .flatMap(fn) — map + flatten one level
iter.flatMap(x => [x, x]).toArray()       // [1, 1, 2, 2, 3, 3, 4, 4, 5, 5]

// .reduce(fn, init) — eagerly accumulate
iter.reduce((acc, x) => acc + x, 0)       // 15

// .forEach(fn) — eagerly iterate side effects
iter.forEach(x => console.log(x))

// .some(fn) / .every(fn) — short-circuits
iter.some(x => x > 3)   // true
iter.every(x => x > 0)  // true

// .find(fn) — first match or undefined
iter.find(x => x > 3)   // 4

// .toArray() — materialize the iterator
iter.take(3).toArray()   // [1, 2, 3]

Why This Matters

Custom iterables — generators, Map/Set/String iterators, DOM NodeLists — now get the same ergonomics as arrays, but lazily. Processing large datasets, streaming responses, or infinite sequences becomes natural:

// Processing a large CSV stream lazily
async function* parseCSVStream(stream) {
  for await (const line of stream) {
    yield parseLine(line)
  }
}

const topActiveUsers = parseCSVStream(stream)
  .filter(row => row.status === 'active')
  .map(row => ({ id: row.id, name: row.name, score: Number(row.score) }))
  .filter(user => user.score > 100)
  .take(10)

for await (const user of topActiveUsers) {
  console.log(user)
}
// Only reads lines until 10 qualifying users found — doesn't read entire file

Float16Array

A new TypedArray for 16-bit IEEE 754 floating-point numbers (half precision). Before ES2026, there was no native JavaScript representation for float16 — values had to be manually packed/unpacked with DataView. This created friction for WebGPU pipelines and ML inference workloads that natively use float16 weights.

// Before ES2026: manual float16 packing
const buffer = new ArrayBuffer(2)
const view = new DataView(buffer)
view.setUint16(0, encodeFloat16(value)) // custom encoding needed

// ES2026: native Float16Array
const weights = new Float16Array(1024)  // 2KB for 1024 weights (vs 4KB Float32)
weights[0] = 1.5
weights[1] = 0.25
console.log(weights[0]) // 1.5 (rounded to nearest float16)

// Interoperating with WebGPU
device.queue.writeBuffer(gpuBuffer, 0, weights.buffer)
// Math.f16round() — companion method
Math.f16round(1.337)  // 1.3369140625 (nearest float16 representable value)
Math.f16round(65504)  // 65504 (max float16)
Math.f16round(65505)  // Infinity (overflow)

Why it matters for ML: Transformer models typically store weights as float16 to halve memory usage. Running inference in the browser with WebGPU now has a first-class JS representation for these values without any manual bit manipulation.


Promise.try()

A small but frequently-needed utility: Promise.try(fn) calls fn and wraps the result in a Promise — whether fn returns synchronously, asynchronously, or throws.

// Before: inconsistent error handling at async/sync boundaries
function processData(input) {
  try {
    const result = mightThrowSync(input) // could throw synchronously
    return Promise.resolve(result).then(transform)
  } catch (error) {
    return Promise.reject(error)
  }
}

// ES2026: Promise.try() handles all cases uniformly
function processData(input) {
  return Promise.try(() => mightThrowSync(input)).then(transform)
}
// Real-world pattern: wrapping callbacks that might be sync or async
function loadConfig(source) {
  return Promise.try(() => {
    if (source === 'env') {
      return parseEnvConfig()         // synchronous
    } else {
      return fetchRemoteConfig(source) // returns a Promise
    }
  })
}

// Both cases handled — sync throws become rejections, async rejections propagate
loadConfig('env')
  .then(config => applyConfig(config))
  .catch(err => console.error('Config load failed:', err))

The real value is at library boundaries where you don't control whether a user-supplied callback is sync or async. Promise.try() eliminates an entire class of "sync function throws, but we expected a Promise rejection" bugs.


RegExp.escape()

Safe construction of regular expressions from user input or dynamic strings. Previously, developers used custom escape utilities (or the popular escape-string-regexp npm package). ES2026 makes this a built-in.

// Before: regex escape was a userland function
function escapeRegExp(string) {
  return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
}

// ES2026: built-in
RegExp.escape('hello.world')     // 'hello\\.world'
RegExp.escape('$100')            // '\\$100'
RegExp.escape('(a|b)')           // '\\(a\\|b\\)'
RegExp.escape('user@email.com')  // 'user@email\\.com'
// Safe dynamic regex construction
function highlightMatches(text, searchTerm) {
  const escapedTerm = RegExp.escape(searchTerm)
  const pattern = new RegExp(`(${escapedTerm})`, 'gi')
  return text.replace(pattern, '<mark>$1</mark>')
}

// Without RegExp.escape, searchTerm = "(" would break the regex
highlightMatches('Contact (555) 123-4567', '(555)')
// → 'Contact <mark>(555)</mark> 123-4567'

This eliminates an entire category of security vulnerability where user input containing regex metacharacters could cause unexpected matching behavior or ReDoS (Regular Expression Denial of Service) patterns.


Error.isError()

Reliable detection of Error objects across realms (iframes, workers, vm contexts). instanceof Error fails for Errors created in a different realm because each realm has its own Error constructor.

// Before: instanceof fails across realms
const iframe = document.createElement('iframe')
document.body.appendChild(iframe)
const iframeError = new iframe.contentWindow.Error('cross-realm')
iframeError instanceof Error  // false! Different realm, different Error class

// ES2026: works across realms
Error.isError(iframeError)  // true
Error.isError(new Error())  // true
Error.isError(new TypeError()) // true (subclasses too)
Error.isError({ message: 'fake' }) // false (plain objects don't qualify)
Error.isError('string error')      // false
Error.isError(null)                // false

This matters most for:

  • Sandboxed code execution (vm module in Node.js)
  • iframe communication
  • Worker thread message passing
  • Framework error boundary implementations that process errors from multiple sources

Import Attributes (Finalized Syntax)

The with syntax for import assertions — previously experimental with the assert keyword — is now finalized in ES2026 with the with keyword.

// ES2026 finalized syntax (was 'assert' in earlier proposals)
import data from './config.json' with { type: 'json' }
import styles from './app.css' with { type: 'css' }

// Dynamic import
const module = await import('./data.json', { with: { type: 'json' } })

// In Node.js 22+
import packageJson from './package.json' with { type: 'json' }
console.log(packageJson.version)

The with syntax replaces the deprecated assert syntax. Update any code using assert { type: 'json' } to with { type: 'json' }.


Late Additions: Temporal, using, and Uint8Array Base64

Three more proposals reached Stage 4 in the final TC39 meetings before the ES2026 cutoff — and they're significant.

Temporal (Stage 4 — ES2026)

The long-awaited replacement for the Date object finally landed. After years in Stage 3, Temporal reached Stage 4 at the March 2026 TC39 meeting:

// ES2026 — Temporal is now stage 4 and in the spec
const now = Temporal.Now.plainDateTimeISO()
const future = now.add({ days: 30 })
const diff = future.since(now)  // { days: 30 }

// Time zones done right
const meeting = Temporal.ZonedDateTime.from({
  year: 2026, month: 6, day: 15, hour: 14,
  timeZone: 'America/New_York'
})
const inTokyo = meeting.withTimeZone('Asia/Tokyo')

Temporal fixes Date's core problems: immutability, proper time zone support, clear distinction between wall-clock time and absolute time, and sensible arithmetic. It's the fix JavaScript developers have needed since 1995.

Explicit Resource Management (using / await using) — ES2026

Deterministic cleanup using the using keyword — works like C# using or Python with:

// ES2026 — using declarations
{
  using file = openFile('data.txt')      // file[Symbol.dispose]() called on scope exit
  using db = openDatabaseConnection()   // db disposed after file
  process(file.contents, db)
}  // Both closed here, even if process() throws

// async version
async function fetchAndProcess() {
  await using client = new HttpClient()  // client[Symbol.asyncDispose]() called
  return await client.get('/api/data')
}

This eliminates entire classes of resource leak bugs. Node.js streams, database connections, file handles — anything implementing Symbol.dispose gets automatic cleanup when the scope exits.

Uint8Array to/from Base64 — ES2026

// ES2026 — native Base64 encoding without atob/btoa
const bytes = new Uint8Array([72, 101, 108, 108, 111])
const base64 = bytes.toBase64()  // 'SGVsbG8='

const decoded = Uint8Array.fromBase64('SGVsbG8=')
// → Uint8Array [72, 101, 108, 108, 111]

// Hex encoding too
const hex = bytes.toHex()    // '48656c6c6f'
const fromHex = Uint8Array.fromHex('48656c6c6f')

Replaces the awkward atob/btoa pattern for binary data encoding and eliminates Buffer.from(str, 'base64') in Node.js.


Array.fromAsync()

Array.from() for async iterables — collects the results of an async generator or async iterable into an array sequentially:

// Before: manual accumulation
const items = []
for await (const item of paginate('/api/items')) {
  items.push(item)
}

// ES2026: clean one-liner
const items = await Array.fromAsync(paginate('/api/items'))

// With a map function (same as Array.from's second argument)
const ids = await Array.fromAsync(paginate('/api/items'), item => item.id)

// Awaiting each element of a sync array of promises (sequential, unlike Promise.all)
const results = await Array.fromAsync([Promise.resolve(1), Promise.resolve(2)])
// [1, 2]

Eliminates the manual for await accumulation pattern that every async data pipeline used to require.


Map.prototype.getOrInsert() / getOrInsertComputed()

The upsert pattern finally becomes a built-in. These two new Map (and WeakMap) methods handle the "get if exists, otherwise insert and return" case that appears in virtually every codebase:

// Before: the classic 3-line boilerplate
if (!cache.has(key)) cache.set(key, [])
cache.get(key).push(value)

// ES2026: single expression
cache.getOrInsert(key, []).push(value)

// getOrInsertComputed: factory only runs if key is missing (avoids unnecessary allocations)
const stats = new Map()
stats.getOrInsertComputed('visits', () => ({ count: 0 })).count++
stats.getOrInsertComputed('visits', () => ({ count: 0 })).count++
// Map { "visits" => { count: 2 } }  — factory only ran once

// Real-world: grouping
function groupBy(items, key) {
  const groups = new Map()
  for (const item of items) {
    groups.getOrInsert(item[key], []).push(item)
  }
  return groups
}

This pattern shows up in memoization, caching, grouping, and graph traversal. getOrInsert for static defaults, getOrInsertComputed when the default is expensive to create.


Math.sumPrecise()

Floating-point summation without precision loss. Uses a compensated summation algorithm that avoids accumulating rounding errors across intermediate steps:

// The classic JS floating-point problem
0.1 + 0.2 + 0.3  // 0.6000000000000001 ← not 0.6

// ES2026: precise
Math.sumPrecise([0.1, 0.2, 0.3])  // 0.6

// Financial data
const prices = [19.99, 9.99, 4.99, 0.03]
Math.sumPrecise(prices)  // 35.0 (exactly, not 35.00000000000001)

// Works with any iterable
Math.sumPrecise(new Set([1.1, 2.2, 3.3]))  // 6.6

Essential for financial calculations, scientific data, and anywhere you're summing many floats. Eliminates the need for decimal libraries like decimal.js for basic precision requirements.


What's Next: Stage 3 Proposals to Watch

Decorators (Stage 3) — class decorators with finalized semantics, TypeScript-compatible.

Async Iterator Helpers.map(), .filter() etc. on async iterables (the async counterpart to Iterator Helpers that shipped in ES2026). Stage 3, imminent.

Pattern Matchingmatch (value) { when ... } syntax. Stage 2, likely 2027+.


Browser and Node.js Support Summary

Most ES2026 features are already shipping:

Feature                  Chrome    Firefox   Safari    Node.js
──────────────────────────────────────────────────────────────
Iterator Helpers         120+      130+      17.4+     22+
Float16Array             120+      129+      18.2+     22+
Promise.try()            128+      134+      17.4+     22+
RegExp.escape()          136+      134+      18.4+     23+
Error.isError()          134+      139+      —         24+
Import attributes        123+      135+      17.2+     v20.10+
Temporal                 144+      139+      partial   24+ (flag)
using / await using      134+      —         —         22+
Uint8Array Base64        121+      —         16.4+     22+
Array.fromAsync()        121+      115+      16.4+     22+
Map.getOrInsert          144+      139+      —         24+
Math.sumPrecise()        137+      136+      —         24+

Most of the core features (Iterator Helpers, Float16Array, Promise.try, Array.fromAsync) are fully available in any Node.js 22+ or current browser. The newer additions (Temporal, Map.getOrInsert, Math.sumPrecise) need Node.js 24+ or Chrome 137+/144+. For legacy browser support, core-js polyfills cover Iterator Helpers and most primitives.


Track npm package adoption of ES2026 polyfills on PkgPulse.

Related: Node.js Native TypeScript Support 2026 · Bun vs Node.js 2026 · JavaScript Bundlers 2026

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.