Hey there! JavaScript became 30 years old in 2025. Despite being created for browsers’ front-end code, it gained more space than expected in the backend. ECMAScript 2025 (ES16), released in June, brought a handful of stable features that make coding smoother without forcing you to kneel before the latest framework altar. In this post, I’ll walk you through the standout additions with practical examples, and for each, I’ll show how you’d do the same thing the old-school way — pre-ES2025. Spoiler: the new features save you some headaches, but the fundamentals still hold strong. Let’s dive in, no fluff, just code!
1. Iterator Helpers: Functional Programming Without the Headache
What’s New: Iterator Helpers introduce a global Iterator object that lets you chain operations like .map()
and .filter()
on any iterable (arrays, sets, generators) in a memory-efficient, lazy way. It’s functional programming without the bloat of intermediate arrays.
Example with Iterator Helpers: Filtering and transforming a leaderboard of scores.
//
// Before
//
const scores = [100, 85, 90, 95, 70];
const topScores = scores
.filter(score => score > 80)
.map(score => `Score: ${score}%`);
console.log(topScores); // ["Score: 100%", "Score: 85%", "Score: 90%", "Score: 95%"]
//
// After ES2025
//
const scores = [100, 85, 90, 95, 70];
const topScores = Iterator.from(scores)
.filter(score => score > 80)
.map(score => `Score: ${score}%`)
.toArray();
console.log(topScores); // ["Score: 100%", "Score: 85%", "Score: 90%", "Score: 95%"]
Why the New Way Wins: The Iterator approach is lazy, processing elements one at a time without creating temporary arrays, which is a lifesaver for large datasets. The old-school array method is simpler to read but can be less performant when chaining multiple operations. That said, for small arrays, the difference is negligible—don’t overcomplicate things just because you can.
Pro Tip: Stick to arrays for quick tasks, but use Iterator when performance matters or when working with non-array iterables like generators.
2. New Set Methods: Set Theory, Meet JavaScript
What’s New: ES2025 adds methods like intersection, union, and difference to Set, making it easier to perform set operations without clunky workarounds.
Example with New Set Methods: Finding shared songs between two playlists.
//
// Before
//
const playlistA = new Set(['Bohemian Rhapsody', 'Imagine', 'Smells Like Teen Spirit']);
const playlistB = new Set(['Imagine', 'Hotel California', 'Bohemian Rhapsody']);
const sharedSongs = new Set([...playlistA].filter(song => playlistB.has(song)));
console.log([...sharedSongs]); // ['Bohemian Rhapsody', 'Imagine']
//
// After ES2025
//
const playlistA = new Set(['Bohemian Rhapsody', 'Imagine', 'Smells Like Teen Spirit']);
const playlistB = new Set(['Imagine', 'Hotel California', 'Bohemian Rhapsody']);
const sharedSongs = playlistA.intersection(playlistB);
console.log([...sharedSongs]); // ['Bohemian Rhapsody', 'Imagine']
Why the New Way Wins: The intersection method is more readable and expressive, directly conveying intent. The old-school approach requires converting the set to an array and back, which is less elegant and slightly less performant for large sets. Still, the old way works fine for simple cases—don’t rewrite your code just to use the new shiny.
Watch Out: Sets are for unique values. If you need ordered lists or complex data, arrays or objects are still your friends.
3. JSON Modules: Importing JSON Without the Fuss
What’s New: ES2025’s JSON Modules let you import JSON files as modules with a simple import statement, using { type: 'json' }
. No more fetch or require hacks.
Example with JSON Modules: Importing a config file for a dashboard.
//
// Before
//
// Browser example with fetch
async function loadConfig() {
const response = await fetch('./config.json');
const config = await response.json();
console.log(config.theme); // 'dark'
console.log(config.charts); // ['bar', 'line']
}
loadConfig();
// Node.js example with require
const config = require('./config.json');
console.log(config.theme); // 'dark'
console.log(config.charts); // ['bar', 'line']
////////////////////////////////
//
// After ES2025
//
// config.json
{
"theme": "dark",
"charts": ["bar", "line"]
}
// app.js
import config from './config.json' with { type: 'json' };
console.log(config.theme); // 'dark'
console.log(config.charts); // ['bar', 'line']
Why the New Way Wins: JSON Modules are cleaner and synchronous (no async boilerplate), making them ideal for static configs. The fetch approach requires async/await, which can complicate simple use cases, and require is Node-specific, breaking in browsers without a bundler. That said, fetch is still necessary for dynamic or remote JSON.
Caveat: Browser support is at 87% (mid-2025). For older environments, stick with fetch or use a bundler like Vite for polyfills.
4. Promise.try: Simplifying Promise Wrappers
What’s New: Promise.try wraps any function (sync or async) in a Promise, catching errors and returning a resolved or rejected Promise. It’s a cleaner way to unify sync and async code.
Example with Promise.try: Validating user input that might throw an error.
//
// Before
//
function validateInput(input) {
if (!input) throw new Error('Input cannot be empty!');
return input.toUpperCase();
}
async function runValidation(input) {
try {
const result = await new Promise((resolve, reject) => {
try {
resolve(validateInput(input));
} catch (err) {
reject(err);
}
});
console.log(result); // 'HELLO'
} catch (err) {
console.log(err.message); // 'Input cannot be empty!'
}
}
runValidation('hello');
runValidation('');
//////////////////////////////
//
// After ES2025
//
function validateInput(input) {
if (!input) throw new Error('Input cannot be empty!');
return input.toUpperCase();
}
const result = await Promise.try(() => validateInput('hello'));
console.log(result); // 'HELLO'
try {
await Promise.try(() => validateInput(''));
} catch (err) {
console.log(err.message); // 'Input cannot be empty!'
}
Why the New Way Wins: Promise.try is concise, eliminating the verbose new Promise wrapper and unifying error handling for sync and async code. The old-school approach works but is clunkier, especially when mixing sync and async logic. Still, if you’re already in an async context, a simple try-catch might suffice.
Think Twice: Don’t overuse Promise.try for straightforward async functions—async/await is often cleaner.
5. RegExp.escape: Goodbye, Manual String Escaping
What’s New: RegExp.escape escapes special regex characters in a string, making it safe for use in a RegExp constructor.
Example with RegExp.escape: Matching user input literally in a search feature.
//
// Before
//
function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
const userInput = 'hello.world*';
const escaped = escapeRegExp(userInput); // 'hello\.world\*'
const regex = new RegExp(escaped);
const text = 'hello.world* is awesome';
console.log(regex.test(text)); // true
////////////////////////////////
//
// After ES2025
//
const userInput = 'hello.world*';
const escaped = RegExp.escape(userInput); // 'hello\.world\*'
const regex = new RegExp(escaped);
const text = 'hello.world* is awesome';
console.log(regex.test(text)); // true
Why the New Way Wins: RegExp.escape is a built-in, reliable solution that saves you from writing (and debugging) your own escape function. The old-school method works but requires maintaining extra code, which can be error-prone if you miss a special character.
Heads-Up: This is niche but invaluable for dynamic regex patterns, like search or validation logic. If you rarely build regexes from user input, you might not need it.
Final Thoughts: New Tools, Same Fundamentals
ES2025’s Iterator Helpers, Set methods, JSON Modules, Promise.try
, and RegExp.escape
are practical additions that streamline common tasks without reinventing the wheel. Compared to the old-school approaches, they offer cleaner syntax, better performance (in some cases), and less boilerplate. But let’s not get carried away — these are tools, not magic bullets. The old ways still work, and sometimes they’re simpler for small projects or when you need broad compatibility.
As always, my advice is to experiment with these features but stay grounded in the fundamentals: write code that solves problems, not code that chases trends. Don’t let the hype on X or at tech conferences trick you into thinking you need to rewrite everything to “stay modern.” JavaScript’s beauty is in its flexibility — and the problems too.
Got thoughts on ES2025? Tried these features or stuck with the classics? Drop a comment below, and let’s geek out over what JavaScript’s doing in 2025!
Originally posted in my blog
Top comments (0)