Back to blog

ECMAScript 2025: The Complete Guide to New JavaScript Features

Hello HaWkers, in June 2025, the 129th General Assembly officially approved the ECMAScript 2025 specification, bringing some of the most awaited features by the JavaScript community.

Are you already using these new features? Let's explore each one in detail, with practical examples you can apply today.

Iterator Helpers: The Headline Feature

The most significant addition in ES2025 is Iterator Helpers - a new built-in Iterator object with functional operators that transform how we work with collections.

Why This Matters

Unlike Arrays that eagerly evaluate and produce intermediate arrays at each stage, Iterator works like other functional-style APIs where each operator is processed element by element.

// Before (Arrays): Creates intermediate arrays
const result = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
  .filter(n => n % 2 === 0)  // Creates array [2, 4, 6, 8, 10]
  .map(n => n * 2)            // Creates array [4, 8, 12, 16, 20]
  .slice(0, 3);               // Creates array [4, 8, 12]

// Now (Iterator): Processes on demand
const iterator = Iterator.from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
  .filter(n => n % 2 === 0)
  .map(n => n * 2)
  .take(3);

// Only processes when consumed
const result = [...iterator]; // [4, 8, 12]

Available Methods

// Iterator.from() - Converts iterables to Iterator
const iter = Iterator.from([1, 2, 3]);
const mapIter = Iterator.from(new Map([['a', 1], ['b', 2]]));

// .map() - Transforms each element
const doubled = Iterator.from([1, 2, 3])
  .map(x => x * 2)
  .toArray(); // [2, 4, 6]

// .filter() - Filters elements
const evens = Iterator.from([1, 2, 3, 4, 5])
  .filter(x => x % 2 === 0)
  .toArray(); // [2, 4]

// .take() - Takes the first N elements
const first3 = Iterator.from([1, 2, 3, 4, 5])
  .take(3)
  .toArray(); // [1, 2, 3]

// .drop() - Ignores the first N elements
const after2 = Iterator.from([1, 2, 3, 4, 5])
  .drop(2)
  .toArray(); // [3, 4, 5]

// .flatMap() - Map + flatten
const flattened = Iterator.from([[1, 2], [3, 4]])
  .flatMap(arr => arr)
  .toArray(); // [1, 2, 3, 4]

Practical Example: Data Processing

// Processing a large CSV file line by line
async function* readCSVLines(filePath) {
  const file = await Deno.open(filePath);
  const decoder = new TextDecoder();

  for await (const chunk of file.readable) {
    const lines = decoder.decode(chunk).split('\n');
    for (const line of lines) {
      yield line;
    }
  }
}

// Using Iterator Helpers to process
const processedData = Iterator.from(readCSVLines('data.csv'))
  .filter(line => line.trim().length > 0)     // Remove empty lines
  .drop(1)                                      // Skip header
  .map(line => line.split(','))                 // Parse CSV
  .filter(([id, name]) => name.startsWith('A')) // Filter by name
  .take(100)                                    // Only first 100
  .map(([id, name, value]) => ({               // Transform to object
    id: parseInt(id),
    name,
    value: parseFloat(value)
  }));

// Consumes only what's needed
for (const record of processedData) {
  console.log(record);
}

New Set Methods

ES2025 adds mathematical methods to Set, making it much more versatile.

Set Operations

const setA = new Set([1, 2, 3, 4, 5]);
const setB = new Set([4, 5, 6, 7, 8]);

// .union() - All elements from both
const union = setA.union(setB);
// Set {1, 2, 3, 4, 5, 6, 7, 8}

// .intersection() - Elements in common
const intersection = setA.intersection(setB);
// Set {4, 5}

// .difference() - Elements in A that are not in B
const difference = setA.difference(setB);
// Set {1, 2, 3}

// .symmetricDifference() - Elements in A or B, but not both
const symDiff = setA.symmetricDifference(setB);
// Set {1, 2, 3, 6, 7, 8}

// .isSubsetOf() - Is A contained in B?
const isSubset = new Set([1, 2]).isSubsetOf(setA);
// true

// .isSupersetOf() - Does A contain B?
const isSuperset = setA.isSupersetOf(new Set([1, 2]));
// true

// .isDisjointFrom() - Do A and B have no elements in common?
const isDisjoint = setA.isDisjointFrom(new Set([10, 11]));
// true

Use Case: Permission System

class PermissionManager {
  constructor() {
    this.rolePermissions = new Map();
  }

  defineRole(role, permissions) {
    this.rolePermissions.set(role, new Set(permissions));
  }

  getUserPermissions(roles) {
    // Union all permissions from all roles
    return roles.reduce(
      (allPerms, role) => allPerms.union(
        this.rolePermissions.get(role) ?? new Set()
      ),
      new Set()
    );
  }

  hasAccess(userRoles, requiredPermissions) {
    const userPerms = this.getUserPermissions(userRoles);
    const required = new Set(requiredPermissions);

    // Check if user has all required permissions
    return required.isSubsetOf(userPerms);
  }

  getMissingPermissions(userRoles, requiredPermissions) {
    const userPerms = this.getUserPermissions(userRoles);
    const required = new Set(requiredPermissions);

    // Return missing permissions
    return required.difference(userPerms);
  }
}

// Usage
const pm = new PermissionManager();

pm.defineRole('admin', ['read', 'write', 'delete', 'manage']);
pm.defineRole('editor', ['read', 'write']);
pm.defineRole('viewer', ['read']);

const userRoles = ['editor', 'viewer'];
const required = ['read', 'write', 'delete'];

console.log(pm.hasAccess(userRoles, required)); // false
console.log([...pm.getMissingPermissions(userRoles, required)]); // ['delete']

Promise.try()

The new Promise.try() mirrors how an async function behaves, allowing a function to run synchronously when possible while still safely catching errors.

// Problem: Inconsistent error handling
function processSyncOrAsync(data) {
  if (typeof data === 'string') {
    // Synchronous - error not caught by .catch()
    return JSON.parse(data);
  } else {
    // Asynchronous
    return fetch(data.url).then(r => r.json());
  }
}

// Old solution (verbose)
function processSafe(data) {
  return new Promise(resolve => {
    resolve(processSyncOrAsync(data));
  });
}

// ES2025 solution (elegant)
function processModern(data) {
  return Promise.try(() => {
    if (typeof data === 'string') {
      return JSON.parse(data);
    } else {
      return fetch(data.url).then(r => r.json());
    }
  });
}

// Uniform usage
processModern('{"name": "test"}')
  .then(data => console.log(data))
  .catch(err => console.error('Error caught:', err));

RegExp.escape()

The new static method RegExp.escape() prevents injection attacks on regular expression strings.

// Problem: User input can break regex
const userInput = "price: $100.00 (discount)";

// Dangerous - special characters break the regex
// const unsafeRegex = new RegExp(userInput); // Error!

// ES2025 solution
const safePattern = RegExp.escape(userInput);
// "price: \\$100\\.00 \\(discount\\)"

const safeRegex = new RegExp(safePattern);
const text = "The price: $100.00 (discount) is great!";

console.log(safeRegex.test(text)); // true

Use in Text Search

function highlightText(text, searchTerm) {
  // Escape special characters from search term
  const escapedTerm = RegExp.escape(searchTerm);
  const regex = new RegExp(`(${escapedTerm})`, 'gi');

  return text.replace(regex, '<mark>$1</mark>');
}

// Safe even with special characters
const result = highlightText(
  "The price is $50.00 (or R$250)",
  "$50.00 (or"
);
// "The price is <mark>$50.00 (or</mark> R$250)"

Float16Array

ES2025 adds Float16Array for working with half-precision floating-point numbers.

// Useful for GPU operations where full precision isn't necessary
const float16Data = new Float16Array([1.5, 2.5, 3.5, 4.5]);

// DataView also gets methods
const buffer = new ArrayBuffer(8);
const view = new DataView(buffer);

view.setFloat16(0, 3.14159, true);  // little-endian
const value = view.getFloat16(0, true);

console.log(value); // ~3.14 (reduced precision)

Why Float16 Matters

// Memory usage comparison
const count = 1000000;

const float64 = new Float64Array(count); // 8MB
const float32 = new Float32Array(count); // 4MB
const float16 = new Float16Array(count); // 2MB

// For machine learning and graphics, Float16 offers:
// - 75% less memory than Float64
// - 50% less memory than Float32
// - Faster processing on modern GPUs

JSON Module Import

ES2025 standardizes direct import of JSON as a module.

// Import JSON directly (local files only)
import appConfig from './config.json' with { type: 'json' };

// Works with dynamic import too
const translations = await import('./i18n/en-US.json', {
  with: { type: 'json' }
});

// Usage
console.log(appConfig.apiUrl);
console.log(translations.default.welcome);

Duplicate Named Capture Groups

Now you can use the same name in two parts of a regex if only one of them can match.

// Before: Names had to be unique
const oldRegex = /(?<year>\d{4})-(?<month>\d{2})|(?<month2>\d{2})\/(?<year2>\d{4})/;

// ES2025: Same name in alternatives
const dateRegex = /(?<year>\d{4})-(?<month>\d{2})|(?<month>\d{2})\/(?<year>\d{4})/;

const match1 = "2025-12".match(dateRegex);
console.log(match1.groups.year);  // "2025"
console.log(match1.groups.month); // "12"

const match2 = "12/2025".match(dateRegex);
console.log(match2.groups.year);  // "2025"
console.log(match2.groups.month); // "12"

RegExp v Flag (Unicode Sets)

The v flag is an upgrade to the u flag, enabling more Unicode-related features.

// Character class intersection
const greekVowels = /[\p{Script=Greek}&&\p{Letter}&&[αεηιουω]]/v;

// Class subtraction
const nonDigitLetters = /[\p{Letter}--\p{Number}]/v;

// Explicit union
const alphanumericPlus = /[[\p{Letter}][\p{Number}][_-]]/v;

Compatibility and Adoption

Browser Support (December 2025)

Feature Chrome Firefox Safari Node.js
Iterator Helpers 122+ 131+ 17.4+ 22+
Set Methods 122+ 127+ 17+ 22+
Promise.try 128+ 132+ 18+ 23+
RegExp.escape 136+ 134+ 18.2+ 23+
Float16Array 127+ 129+ 18+ 22+

How to Use Today

// Support check
const supportsIteratorHelpers = typeof Iterator !== 'undefined';
const supportsSetMethods = typeof Set.prototype.union === 'function';
const supportsPromiseTry = typeof Promise.try === 'function';

// Polyfills available
// npm install core-js
import 'core-js/actual/iterator';
import 'core-js/actual/set';
import 'core-js/actual/promise';

Conclusion

ECMAScript 2025 brings features that the JavaScript community has been waiting for years. Iterator Helpers and Set Methods in particular fundamentally change how we work with collections, offering cleaner and more performant code.

The best part is that many of these features are already available in modern browsers. Start using them today and improve the quality of your JavaScript code.

If you want to deepen your knowledge in modern JavaScript, I recommend checking out another article: Passkeys and WebAuthn: The Complete Guide where you'll discover how to implement modern authentication in your applications.

Let's go! 🦅

📚 Want to Deepen Your JavaScript Knowledge?

This article covered the new ES2025 features, but there's much more to explore in modern development.

Developers who invest in solid, structured knowledge tend to have more opportunities in the market.

Complete Study Material

If you want to master JavaScript from basics to advanced, I've prepared a complete guide:

Investment options:

  • 1x of $4.90 on card
  • or $4.90 at sight

👉 Learn About JavaScript Guide

💡 Material updated with industry best practices

Comments (0)

This article has no comments yet 😢. Be the first! 🚀🦅

Add comments