New features in ECMAScript 2025

In June 2025, the 129th General Assembly approved the ECMAScript 2025 language specification, which means that ECMAScript 2025 is officially a standard now.

In this blog, we will explore the new features in detail.

Iterator helpers

MDN defines iterators as

Iterators are objects which implement the Iterator protocol by having a next() method that returns an object with two properties:

value: The next value in the iteration sequence.

done: This is true if the last value in the sequence has already been consumed. If value is present alongside done, it is the iterator’s return value.

Before the standardization of iterator helper methods in ECMAScript 2025, JavaScript lacked built-in methods like .map(), .filter(), .reduce(), etc., directly on iterator objects. This made working with iterators less convenient as compared to arrays. Developers had to use alternative approaches to achieve similar functionality:

  • Converting iterators to arrays, losing the lazy and potentially infinite behavior of iterators
  • Manual looping
  • Using 3rd party libraries

ECMAScript 2025 added new methods on iterator to allow general usage and consumption of iterators.

  1. map() - Allows users to apply a function to every element returned from an iterator
  2. filter() - Allows users to skip values from an iterator which do not pass a filter function
  3. take() - Returns an iterator with items from the original iterator from 0 until the limit
  4. drop()- Returns an iterator of items after the limit
  5. flatMap() - Returns an iterator of flat values
  6. reduce() - Allows users to apply a function to every element returned from an iterator, while keeping track of the most recent result of the reducer
  7. toArray() - Returns an Array containing the values from the iterator
  8. forEach() - Allows to loop over iterator
  9. some() - Checks if any value in the iterator matches a given predicate,
  10. every() - Checks if every value generated by the iterator passes the test function
  11. find() - Finds the first element in an iterator that matches.
  12. Iterator.from() - Static method which creates a new Iterator object from an iterator or iterable object

New Set methods

The Set object allows us to store unique values of any type.

ECMAScript 2025, added new methods to Set that allow us to compose sets like we would with mathematical operations, making it much more versatile, matching the needs of modern developers and applications.

  • union
  • intersection
  • difference
  • symmetricDifference
  • isSubsetOf
  • isSupersetOf
  • isDisjointFrom
const setA = new Set([1, 2, 3]);
const setB = new Set([3, 4, 5]);

setA.union(setB);              // Set {1, 2, 3, 4, 5}   (setA U setB)
setA.intersection(setB);       // Set {3}               (setA ∩ setB)
setA.difference(setB);         // Set {1, 2}            (setA \ setB)
setA.symmetricDifference(setB);// Set {1, 2, 4, 5}      (setA \ setB) U (setB \ setA)
setA.isSubsetOf(setB);         // false                 (setA ⊆ setB)
setA.isSupersetOf(setB);       // false                 (setA ⊇ setB)
setA.isDisjointFrom(setB);     // false                 (setA ∩ setB = ∅)

Import Attributes and JSON modules

Browsers did not natively support importing JSON files. We are able to import non-JS files due to bundlers like webpack.

ECMAScript 2025 introduced the new import attributes which tells the runtime about how a module should be loaded, including the behavior of module resolution, fetching, parsing, and evaluation. It ensures module types are always strictly validated and prevents any security risks.

ImportDeclaration

  import config from "./config.json" with { type: "json" };

  const jsonConfig = await import("./config.json", { with: { type: "json" } });

ExportDeclaration

  export { config } from "./config.json" with { type: "json" };

The type attribute is used to indicate a module type. For now, the supported type is json which is a part of json modules proposal.

RegExp Escaping

When using regular expressions, we often come across scenarios where we don’t want any regex special characters in that string (like ., *, or ?) to act as regex operators.

Consider a situation where we want to replace ‘file.txt’ with ‘sample.txt’.

  const userInput = 'file.';
  const text = "Please open the file.txt now! Don't open image files";
  const pattern = new RegExp(userInput, 'g'); 
  const newText = text.replace(pattern, "sample.");

  console.log(newText); // Please open the sample.txt now! Don't open image sample.

While ‘file.txt’ was correctly replaced by ‘sample.txt’, the word ‘files’ was also replaced unexpectedly.

This happened because the string ‘file.’ was passed directly into the RegExp constructor, and the . character was interpreted as a wildcard matching any single character, rather than a literal dot.

To fix this problem we need to write a function which would escape wildcards.

Thankfully, as of ECMAScript 2025, we can use the standardized RegExp.escape() method to handle escaping reliably and concisely.

const userInput = 'file.';
const text = "Please open the file.txt now! Don't open image files";
const pattern = new RegExp(RegExp.escape(userInput));
const newText = text.replace(pattern, "sample.");

console.log(newText); // Please open the sample.txt now! Don't open image files.

Now, RegExp.escape('file.') produces file\\., so the literal dot is properly escaped and only the exact string matches.

Fun fact: This feature was proposed 15 years ago.

Regular Expression Pattern Modifiers

We have been applying expression flags (such as i, m, s, and u) to the entire regular expression.

With ECMAScript 2025, it is now possible to apply these flags to specific parts of a regular expression using inline modifiers. This gives us finer control over how different segments of a pattern are matched.

Check out this example that applies case insensitivity only to the word ‘blogs’, while ensuring that the word ‘Saeloun’ remains in title case.

  const pattern = /^(?i:blogs) by Saeloun$/;

  pattern.test("blogs by Saeloun"); // true
  pattern.test("Blogs by Saeloun"); // true
  pattern.test("bloGs by Saeloun"); // true
  pattern.test("blogs by saeloun"); // false  

Promise.try()

When dealing with APIs that accept a callback function, this callback may be:

  • Synchronous: Returns a result immediately or throws an error synchronously

  • Asynchronous: Returns a Promise or triggers asynchronous work

To handle everything uniformly we wrap the result in a promise.

Promise.resolve(func())

But this has a problem:

If func() throws an error synchronously (before returning anything), that error is thrown immediately, not caught, and not turned into a rejected Promise.

function syncThrows() { throw new Error("Synchronous error!"); }

Promise.resolve(syncThrows()) // error thrown before Promise.resolve can run
  .catch(error => console.log("Caught:", error.message));


// Output
  > Uncaught Error: Synchronous error!
    at syncThrows (<anonymous>:1:31)
    at <anonymous>:1:17 

To fix this issue we need to lift function call result into a promise.

function syncThrows() { throw new Error("Synchronous error!"); }

Promise.resolve()
  .then(syncThrows)
  .catch(error => console.log("Caught:", error.message));

// Output
  > Caught: Synchronous error!

Yay! This works great!

It captures any thrown exceptions and wraps any returned value in a Promise. However, it unnecessarily delays the execution of syncThrows to the next event loop tick.

To make syncThrows run on the same tick, we can make the following change

new Promise(resolve => resolve(syncThrows()))
 .catch(error => console.log("Caught:", error.message));

This achieves the goal, but is not intuitive nor easy to remember.

We can use the newly introduced Promise.try() in ECMAScript 2025. It closely mirrors how an async function behaves, allowing a function to run synchronously when possible, while still safely catching errors and returning a Promise to work with.

It’s a concise and reliable solution!

function syncReturns() { 
  return 42; 
}
function syncThrows()  { 
  throw new Error("Synchronous error!"); 
}
function asyncReturns() { 
  return Promise.resolve("Async result"); 
}

// Synchronous return
Promise.try(() => syncReturns())
  .then(result => console.log("Result:", result)) // Result: 42
  .catch(err => console.error("Error:", err.message));

// Synchronous throw
Promise.try(() => syncThrows())
  .then(result => console.log("Result:", result))
  .catch(err => console.error("Caught:", err.message)); // Caught: Synchronous error!

// Asynchronous return
Promise.try(() => asyncReturns())
  .then(result => console.log("Result:", result)) // Result: Async result
  .catch(err => console.error("Error:", err.message));

Float16Array

ECMAScript 2025 added a new kind of TypedArray, Float16Array, to complement the existing Float32Array and Float64Array.

It also added two DataView methods, getFloat16 and setFloat16, to handle half-precision floats, mirroring the current methods for full and double precision.

This feature is useful for GPU operations, where full precision often isn’t necessary but memory constraints are serious.

Need help on your Ruby on Rails or React project?

Join Our Newsletter