Here are Some new Features in Javascript
Updated: May 5, 2026
TL;DR
ES2024 and ES2025 delivered substantial additions: Promise.withResolvers(), Object.groupBy / Map.groupBy, Set theory operations (union, intersection, difference), and the Iterator Helpers (lazy .map, .filter, .take, .toArray on iterators) that all shipped to every current evergreen browser. This post walks through what's standardized today, what's still on the way, and the proposals that have been withdrawn since the original 2023 draft.
JavaScript's evolution never stops. Every year, TC39 (the standards committee) advances new features from proposal through standardization. By mid-2026, ES2024 features are widely available across modern browsers and Node.js, ES2025 has been finalized (Iterator Helpers, Set methods, RegExp v flag escapes, Promise.try, and more), and several proposals are at Stage 3 awaiting final implementation. In this post, we'll cover the most useful additions from recent years, the proposals that are realistic to plan for, and a few that have been withdrawn or restructured.
ES2024 Features
Promise.withResolvers()
Before ES2024, creating a Promise and manually controlling its resolution required storing references outside the constructor. Promise.withResolvers() fixes this.
// Before ES2024 — verbose
let resolve, reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
// ES2024 — clean
const { promise, resolve, reject } = Promise.withResolvers();
// Real-world use case: event-driven resolution
const click = Promise.withResolvers();
document.getElementById('button').addEventListener('click', () => {
click.resolve('Button clicked!');
});
const result = await click.promise;
console.log(result); // Button clicked!
Browser Support: Chrome 119+, Firefox 121+, Safari 17.4+, Node.js 22+
Array Grouping: Object.groupBy() and Map.groupBy()
Grouping data by a key function is a common operation. ES2024 finally provided native support — but note: there is no Array.groupBy(). The original Array.prototype.group() / Array.prototype.groupBy() proposal was withdrawn over web-compatibility concerns and replaced with two static methods on Object and Map.
const products = [
{ id: 1, name: 'Laptop', category: 'Electronics', price: 1200 },
{ id: 2, name: 'Chair', category: 'Furniture', price: 150 },
{ id: 3, name: 'Monitor', category: 'Electronics', price: 400 },
{ id: 4, name: 'Desk', category: 'Furniture', price: 500 }
];
// Object.groupBy() - returns a plain object keyed by the callback's return string
const byCategory = Object.groupBy(products, p => p.category);
// Result: { Electronics: [...], Furniture: [...] }
console.log(byCategory.Electronics); // Array of electronics
// You can use any iterable + any string-returning key function
const grouped = Object.groupBy(products, p => p.price > 500 ? 'expensive' : 'affordable');
// Result: { expensive: [...], affordable: [...] }
// Map.groupBy() - same idea, but the result is a Map (so keys can be objects)
const byCategoryMap = Map.groupBy(products, p => p.category);
// byCategoryMap.get('Electronics') -> Array of electronics
Browser Support: Chrome 117+, Firefox 119+, Safari 17.4+, Node.js 21+
Set Methods: Union, Intersection, Difference
Sets received powerful set-theory operations.
const admins = new Set(['alice', 'bob', 'charlie']);
const moderators = new Set(['bob', 'david', 'eve']);
// Union: all members from both sets
const allStaff = admins.union(moderators);
console.log(allStaff); // Set(5) { 'alice', 'bob', 'charlie', 'david', 'eve' }
// Intersection: members in both sets
const staffWithBothRoles = admins.intersection(moderators);
console.log(staffWithBothRoles); // Set(1) { 'bob' }
// Difference: members in first set but not second
const adminsOnly = admins.difference(moderators);
console.log(adminsOnly); // Set(2) { 'alice', 'charlie' }
// Symmetric difference: members in either set but not both
const uniqueToEach = admins.symmetricDifference(moderators);
console.log(uniqueToEach); // Set(4) { 'alice', 'charlie', 'david', 'eve' }
// Relationship checks
console.log(admins.isSubsetOf(allStaff)); // true
console.log(admins.isSupersetOf(moderators)); // false
console.log(admins.isDisjointFrom(new Set(['frank', 'grace']))); // true
Browser Support: Chrome 122+, Firefox 127+, Safari 17+, Node.js 22+
find / findIndex / findLast / findLastIndex
findIndex() returns the matching element's index but not the element itself, so people often want both. The cleanest pattern is to grab the element with find (or findLast) and the index with findIndex (or findLastIndex) — using two distinct variable names.
const users = [
{ id: 1, name: 'Alice', active: true },
{ id: 2, name: 'Bob', active: false },
{ id: 3, name: 'Charlie', active: true }
];
// Traditional findIndex - returns only the index
const bobIndex = users.findIndex(u => u.name === 'Bob');
const bobByIndex = users[bobIndex];
// Or grab the element directly with find
const bob = users.find(u => u.name === 'Bob');
// findLast / findLastIndex (ES2023) walk from the end
const lastActive = users.findLast(u => u.active); // Charlie
const lastActiveIndex = users.findLastIndex(u => u.active); // 2
Iterator Helpers (ES2025)
Iterator Helpers reached Stage 4 at the October 2024 TC39 plenary and were folded into ES2025. They give iterators their own lazy .map, .filter, .take, .drop, .flatMap, .reduce, .forEach, .some, .every, .find, and .toArray. Crucially they are lazy — .map and .filter return iterator helpers that do not materialize an intermediate array; only .toArray allocates one.
// Lazy mapping + filtering over an iterable
const numbers = [1, 2, 3, 4, 5];
const doubled = Iterator.from(numbers)
.map(n => n * 2)
.filter(n => n > 4)
.toArray();
console.log(doubled); // [6, 8, 10]
// Chaining over a generator without building intermediate arrays
function* generateUsers() {
yield { id: 1, age: 25 };
yield { id: 2, age: 30 };
yield { id: 3, age: 35 };
}
const adults = Iterator.from(generateUsers())
.filter(u => u.age >= 30)
.toArray();
Status: Standardized in ES2025. Available in Chrome 122+, Firefox 131+, Safari 18.4+, Node.js 22+, Bun 1.1.31+, and Deno 2 (Baseline since March 2025). TypeScript ships the types in lib.es2025.iterator.d.ts from 5.6 onward.
Decorators (Stage 3)
Decorators allow metadata-driven programming and are widely used in TypeScript and frameworks. The current Stage 3 design is the "standard decorators" proposal — distinct from the older legacy experimentalDecorators design that earlier TypeScript versions supported.
// Standard (Stage 3) decorator — TypeScript 5.0+ implements this without flags
function readonly<This, Value>(
_target: ClassAccessorDecoratorTarget<This, Value>,
context: ClassAccessorDecoratorContext<This, Value>
): ClassAccessorDecoratorResult<This, Value> {
return {
set() {
throw new Error(`Cannot assign to read-only property '${String(context.name)}'`);
},
};
}
class User {
@readonly
accessor id: number = 1;
name: string = 'Alice';
}
const user = new User();
user.name = 'Bob'; // OK
// user.id = 2; // Error at runtime: Cannot assign to read-only property 'id'
Status: Stage 3 since 2023. Production-ready via TypeScript 5+ and Babel; not yet shipped natively in any browser engine. Frameworks such as Angular and Lit have adopted the Stage 3 form; legacy decorators remain in TypeScript behind experimentalDecorators for older NestJS / TypeORM codebases.
Record and Tuple — WITHDRAWN
Earlier drafts of this post said Record and Tuple were a Stage 2 proposal. That is no longer true: at the TC39 plenary on April 14, 2025, consensus was reached to withdraw the Record and Tuple proposal. The proposal could not gain further consensus for adding new primitive types to the language, and the GitHub repository has been archived.
If you need value-semantic immutable data today, the practical options are:
- Plain objects / arrays plus
Object.freeze(reference equality, not value equality) - Libraries like Immer or Immutable.js
- The follow-up TC39 effort sometimes called "Composites" / "Tuples and Records v2", which targets new immutable objects (with
Object.is-style structural equality on opt-in) rather than new primitives — still early-stage and not ready for production
// The original syntax (#{...} / #[...]) will NOT ship as primitives:
// const point = #{ x: 1, y: 2 }; // syntax error in every engine — proposal withdrawn
// Today's pragmatic substitute
const point = Object.freeze({ x: 1, y: 2 });
// point.x = 3; // silently ignored in non-strict mode, throws in strict mode
Status: Withdrawn (April 14, 2025). Do not plan around #{} / #[] syntax.
Pattern Matching (Stage 1)
Pattern matching is a long-running proposal for destructuring-style matching in expression form — closer to Rust's match than to a switch. As of the most recent TC39 cycles in 2025, the proposal remains at Stage 1: the design space is still being debated, and several competing sub-proposals exist for syntax and semantics. (Some third-party blog posts have reported it as Stage 3 — that is not reflected in the official TC39 proposals list as of May 2026.) Realistic native shipping is 2027+.
// Conceptual syntax — STILL BEING DEBATED, do not use in production.
// The actual final syntax may differ.
const response = { status: 200, data: [{ id: 1 }] };
match (response) {
when ({ status: 200, data }) {
console.log('Success:', data);
}
when ({ status: 404 }) {
console.log('Not found');
}
when ({ status }) {
console.log('Error:', status);
}
}
Status: Stage 1 (early exploration; not implemented in any engine; final syntax still in flux).
Browser and Runtime Compatibility Summary
Compatibility data below is sourced from MDN and caniuse.com as of May 2026. Always re-check caniuse.com against your actual browserslist target before shipping — engines ship features faster than blog posts get updated.
| Feature | Chrome | Firefox | Safari | Node.js | Status |
|---|---|---|---|---|---|
Promise.withResolvers() | 119+ | 121+ | 17.4+ | 22+ | ES2024 (shipped) |
Object.groupBy / Map.groupBy | 117+ | 119+ | 17.4+ | 21+ | ES2024 (shipped) |
Set methods (union, intersection, etc.) | 122+ | 127+ | 17+ | 22+ | ES2025 (shipped) |
Iterator Helpers (Iterator.from, lazy .map / .filter) | 122+ | 131+ | 18.4+ | 22+ | ES2025 (shipped, Baseline March 2025) |
| Decorators (Stage 3) | — | — | — | — | Stage 3 — TypeScript 5+ / Babel only |
Record / Tuple (#{} / #[]) | — | — | — | — | Withdrawn April 14, 2025 |
Pattern Matching (match expression) | — | — | — | — | Stage 1 (design in flux) |
Best Practices for Using New Features
1. Check Your Target Environment
If your app needs to support older browsers, use a transpiler like Babel.
// babel.config.js
module.exports = {
presets: [
['@babel/preset-env', {
targets: '> 0.5%, last 2 versions, not dead'
}]
]
};
2. Polyfill When Needed
For features that ship in newer engines but not in your support floor, ship a tiny polyfill or use core-js / es-shims.
// polyfill-example.js
if (!Promise.withResolvers) {
Promise.withResolvers = function() {
let resolve, reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
return { promise, resolve, reject };
};
}
3. Test in Your Target Environments
Use caniuse.com or browserslist to verify support.
{
"browserslist": [
"last 2 versions",
"> 0.5%",
"not dead"
]
}
Conclusion
ES2024 delivered substantial improvements with Promise.withResolvers(), Object.groupBy / Map.groupBy, and the foundation for set theory operations. ES2025 then shipped the Set methods and Iterator Helpers across every current evergreen engine. Decorators (Stage 3) remain a transpiler story for now, Pattern Matching is still early Stage 1, and the Record and Tuple proposal has been withdrawn — so any older guides telling you to plan around #{} / #[] are out of date. Stay current with the TC39 proposals list (tc39.es/proposals), but prioritize features already in your target browsers, lean on transpilers when you genuinely need bleeding-edge syntax, and remember that what gets withdrawn is just as important as what advances.