JavaScript ES2024+ Features Cheat Sheet
Quick reference for ES2024 finalized features and upcoming ES2025+ proposals — array grouping, iterator helpers, set methods, Temporal API, decorators, and more.
Array Grouping (ES2024)
Group iterable elements by a callback return value. Replaces the need for manual reduce() grouping patterns.
| Method | Syntax | Returns |
|---|---|---|
Object.groupBy() | Object.groupBy(iterable, callback) | Null-prototype object with grouped arrays |
Map.groupBy() | Map.groupBy(iterable, callback) | Map with grouped arrays (keys can be any type) |
const inventory = [
{ name: 'apples', type: 'fruit', qty: 5 },
{ name: 'bananas', type: 'fruit', qty: 0 },
{ name: 'carrots', type: 'vegetable', qty: 8 },
];
// Group by type
const grouped = Object.groupBy(inventory, item => item.type);
// { fruit: [{name:'apples',...}, {name:'bananas',...}], vegetable: [{name:'carrots',...}] }
// Group by stock status
const stock = Object.groupBy(inventory, ({ qty }) =>
qty > 0 ? 'inStock' : 'outOfStock'
);
// Map.groupBy — useful when keys are objects
const byStatus = Map.groupBy(inventory, ({ qty }) =>
qty > 0 ? { status: 'ok' } : { status: 'empty' }
);
Support: Chrome 117+, Firefox 119+, Safari 17.4+, Node 21+. All major browsers.
Promise.withResolvers() (ES2024)
Creates a deferred promise, returning the promise along with its resolve and reject functions.
| Feature | Syntax | Description |
|---|---|---|
Promise.withResolvers() | const { promise, resolve, reject } = Promise.withResolvers(); | Returns object with promise + resolve/reject callbacks |
// Before: deferred promise pattern
let resolve, reject;
const promise = new Promise((res, rej) => { resolve = res; reject = rej; });
// After: ES2024
const { promise, resolve, reject } = Promise.withResolvers();
// Practical use: event-driven resolution
function waitForEvent(target, eventName) {
const { promise, resolve } = Promise.withResolvers();
target.addEventListener(eventName, resolve, { once: true });
return promise;
}
const click = await waitForEvent(button, 'click');
Support: Chrome 119+, Firefox 121+, Safari 17.4+, Node 22+.
ArrayBuffer Transfer & Resize (ES2024)
Transfer ownership of ArrayBuffers and create resizable/growable buffers for efficient memory management.
| Method | Syntax | Description |
|---|---|---|
transfer() | buf.transfer(newByteLength?) | Transfers ownership; original becomes detached |
transferToFixedLength() | buf.transferToFixedLength(newLen?) | Transfers to a non-resizable buffer |
resize() | buf.resize(newByteLength) | Resizes in-place (resizable buffers only) |
detached | buf.detached | Boolean: true if buffer has been transferred |
// Resizable ArrayBuffer
const buf = new ArrayBuffer(256, { maxByteLength: 1024 });
buf.resize(512); // Grow in-place
buf.resizable; // true
// Transfer ownership (zero-copy where possible)
const newBuf = buf.transfer();
buf.detached; // true — original is unusable
// Growable SharedArrayBuffer
const shared = new SharedArrayBuffer(256, { maxByteLength: 1024 });
shared.grow(512);
Support: Chrome 114+, Firefox 122+, Safari 17.4+, Node 22+.
String.isWellFormed() & toWellFormed() (ES2024)
Validate and fix lone surrogates in strings to ensure well-formed UTF-16, preventing issues with encodeURIComponent() and other APIs.
| Method | Syntax | Description |
|---|---|---|
isWellFormed() | str.isWellFormed() | Returns true if string has no lone surrogates |
toWellFormed() | str.toWellFormed() | Replaces lone surrogates with U+FFFD |
"hello".isWellFormed(); // true
"\uD800".isWellFormed(); // false (lone surrogate)
"\uD800".toWellFormed(); // "\uFFFD" (replacement char)
// Safe encoding
const input = getUserInput();
if (input.isWellFormed()) {
const encoded = encodeURIComponent(input);
} else {
const safe = input.toWellFormed();
}
Support: Chrome 111+, Firefox 119+, Safari 16.4+, Node 20+.
Atomics.waitAsync() (ES2024)
Non-blocking version of Atomics.wait() that returns a promise, usable on the main thread.
| Method | Syntax | Description |
|---|---|---|
Atomics.waitAsync() | Atomics.waitAsync(typedArray, index, value, timeout?) | Returns { async: true, value: Promise } or synchronous result |
const sab = new SharedArrayBuffer(4);
const i32 = new Int32Array(sab);
// Main thread — non-blocking wait
const result = Atomics.waitAsync(i32, 0, 0);
if (result.async) {
result.value.then(status => {
console.log(status); // "ok" or "timed-out"
});
}
// Worker thread — notify
Atomics.store(i32, 0, 1);
Atomics.notify(i32, 0);
Support: Chrome 87+, Firefox 124+, Safari 17.4+, Node 16+.
RegExp v Flag — Set Notation (ES2024)
The v flag upgrades the u flag with set operations inside character classes and support for Unicode string properties.
| Operation | Syntax | Example |
|---|---|---|
| Intersection | [A&&B] | /[\p{Script=Greek}&&\p{Letter}]/v |
| Subtraction | [A--B] | /[\p{Decimal_Number}--[0-9]]/v |
| Union | [AB] | /[[\p{Emoji}\p{Letter}]]/v (same as without v) |
| String properties | \p{...} | /\p{RGI_Emoji}/v matches multi-codepoint emoji |
// Match Greek letters only (intersection)
const greek = /[\p{Script=Greek}&&\p{Letter}]/v;
greek.test('\u03B1'); // true (alpha)
// Match non-ASCII digits (subtraction)
const nonAsciiDigits = /[\p{Decimal_Number}--[0-9]]/v;
// Match multi-codepoint emoji (string properties)
const emoji = /^\p{RGI_Emoji}$/v;
emoji.test('\u{1F468}\u200D\u{1F469}\u200D\u{1F467}'); // true (family emoji)
Support: Chrome 112+, Firefox 116+ (partial), Safari 17+, Node 20+.
Temporal API (Stage 3 — Coming Soon)
A modern replacement for Date with immutable types, proper time zone support, and correct arithmetic. Use @js-temporal/polyfill today.
| Type | Example | Use Case |
|---|---|---|
Temporal.Now | Temporal.Now.plainDateISO() | Current date/time in various forms |
Temporal.PlainDate | Temporal.PlainDate.from('2024-06-15') | Calendar date without time or zone |
Temporal.PlainTime | Temporal.PlainTime.from('14:30:00') | Wall-clock time without date or zone |
Temporal.PlainDateTime | Temporal.PlainDateTime.from('2024-06-15T14:30') | Date + time without zone |
Temporal.ZonedDateTime | Temporal.ZonedDateTime.from('2024-06-15T14:30[America/New_York]') | Full date/time with time zone |
Temporal.Duration | Temporal.Duration.from({ hours: 1, minutes: 30 }) | Lengths of time for arithmetic |
Temporal.Instant | Temporal.Now.instant() | Exact point on the UTC timeline |
// Current date
const today = Temporal.Now.plainDateISO();
// Date arithmetic — correct across DST
const deadline = today.add({ months: 3, days: 10 });
// Duration between dates
const diff = deadline.since(today);
console.log(diff.toString()); // "P3M10D"
// Zoned date/time — handles time zones properly
const meeting = Temporal.ZonedDateTime.from({
timeZone: 'America/New_York',
year: 2024, month: 11, day: 3,
hour: 1, minute: 30,
}); // Correctly handles DST fall-back
// Compare dates
Temporal.PlainDate.compare(date1, date2); // -1, 0, or 1
Support: Polyfill available via @js-temporal/polyfill. Native support shipping progressively in 2025.
Iterator Helpers (ES2025)
Built-in methods on iterator prototypes for lazy, chainable transformations without intermediate arrays.
| Method | Syntax | Description |
|---|---|---|
.map() | iter.map(fn) | Lazily transforms each value |
.filter() | iter.filter(fn) | Lazily keeps values matching predicate |
.take() | iter.take(n) | Takes first n values then stops |
.drop() | iter.drop(n) | Skips first n values |
.toArray() | iter.toArray() | Collects remaining values into array |
.forEach() | iter.forEach(fn) | Iterates eagerly with side effects |
.find() | iter.find(fn) | Returns first matching value |
.some() | iter.some(fn) | True if any value matches |
.every() | iter.every(fn) | True if all values match |
.reduce() | iter.reduce(fn, init) | Reduces values to a single result |
.flatMap() | iter.flatMap(fn) | Maps and flattens one level |
Iterator.from() | Iterator.from(iterable) | Wraps any iterable/iterator to get helpers |
// Chain lazy operations on generators
function* naturals() { let i = 1; while (true) yield i++; }
const result = naturals()
.filter(n => n % 2 === 0) // even numbers
.map(n => n ** 2) // square them
.take(5) // first 5
.toArray(); // [4, 16, 36, 64, 100]
// Works on Map, Set, etc.
const map = new Map([['a', 1], ['b', 2], ['c', 3]]);
map.values().filter(v => v > 1).toArray(); // [2, 3]
// Wrap any iterable
Iterator.from('hello').take(3).toArray(); // ['h', 'e', 'l']
Support: Chrome 122+, Firefox 131+, Safari 18+, Node 22+.
Set Methods (ES2025)
Mathematical set operations built into Set.prototype — no more manual iteration for unions and intersections.
| Method | Syntax | Description |
|---|---|---|
union() | setA.union(setB) | All elements from both sets |
intersection() | setA.intersection(setB) | Elements in both sets |
difference() | setA.difference(setB) | Elements in A but not in B |
symmetricDifference() | setA.symmetricDifference(setB) | Elements in either set but not both |
isSubsetOf() | setA.isSubsetOf(setB) | True if all A elements are in B |
isSupersetOf() | setA.isSupersetOf(setB) | True if A contains all B elements |
isDisjointFrom() | setA.isDisjointFrom(setB) | True if sets share no elements |
const frontend = new Set(['js', 'css', 'html', 'react']);
const backend = new Set(['js', 'python', 'go', 'sql']);
frontend.union(backend);
// Set {'js', 'css', 'html', 'react', 'python', 'go', 'sql'}
frontend.intersection(backend);
// Set {'js'}
frontend.difference(backend);
// Set {'css', 'html', 'react'}
frontend.symmetricDifference(backend);
// Set {'css', 'html', 'react', 'python', 'go', 'sql'}
new Set([1, 2]).isSubsetOf(new Set([1, 2, 3])); // true
new Set([1, 2]).isDisjointFrom(new Set([3, 4])); // true
Support: Chrome 122+, Firefox 127+, Safari 17+, Node 22+.
Decorators (Stage 3)
The @decorator syntax for classes and class elements. Standardizes a pattern widely used via TypeScript and Babel.
| Target | Syntax | Example Use Case |
|---|---|---|
| Class | @log class Foo {} | Add logging, register in DI container |
| Method | @bound method() {} | Auto-bind, memoize, measure performance |
| Field | @validate name = '' | Validation, type checking, reactivity |
| Accessor | @reactive accessor count = 0 | Observable state, lazy initialization |
// A decorator is a function that wraps a class element
function log(target, context) {
if (context.kind === 'method') {
return function (...args) {
console.log(`Calling ${context.name} with`, args);
return target.apply(this, args);
};
}
}
function bound(target, context) {
if (context.kind === 'method') {
context.addInitializer(function () {
this[context.name] = this[context.name].bind(this);
});
}
}
class Router {
@log
@bound
handleRequest(req) {
return this.process(req);
}
}
Support: Available in TypeScript 5.0+ and Babel. Native browser support in progress.
Pipeline Operator (Stage 2)
The |> operator pipes a value into a function, improving readability of deeply nested function calls.
| Feature | Syntax | Equivalent |
|---|---|---|
| Basic pipe | value |> fn | fn(value) |
| Chained pipe | value |> fn1 |> fn2 | fn2(fn1(value)) |
| With arrow | value |> (x => x + 1) | (x => x + 1)(value) |
// Before: nested calls
const result = capitalize(removeDashes(trim(input)));
// After: pipeline operator
const result = input
|> trim
|> removeDashes
|> capitalize;
// With arrow functions for partial application
const result = input
|> (s => s.trim())
|> (s => s.replace(/-/g, ' '))
|> (s => s.toUpperCase());
Support: Stage 2 proposal. Available via Babel plugin @babel/plugin-proposal-pipeline-operator.
Record & Tuple (Stage 2)
Deeply immutable data structures with value-based equality. Records are like frozen objects; Tuples are like frozen arrays.
| Type | Syntax | Key Property |
|---|---|---|
| Record | #{ key: value } | Immutable object, compared by value |
| Tuple | #[a, b, c] | Immutable array, compared by value |
// Records — immutable objects with value equality
const r1 = #{ name: "Alice", age: 30 };
const r2 = #{ name: "Alice", age: 30 };
r1 === r2; // true (value equality!)
// Tuples — immutable arrays with value equality
const t1 = #[1, 2, 3];
const t2 = #[1, 2, 3];
t1 === t2; // true
// Nested records and tuples
const data = #{ users: #[#{ name: "Alice" }, #{ name: "Bob" }] };
// Can be used as Map keys (value equality)
const map = new Map();
map.set(#{ x: 1, y: 2 }, "point A");
map.get(#{ x: 1, y: 2 }); // "point A"
Support: Stage 2 proposal. Available via Babel plugin @babel/plugin-proposal-record-and-tuple with @bloomberg/record-tuple-polyfill.
Frequently Asked Questions
What are the main new features in JavaScript ES2024?
ES2024 (ES15) includes Object.groupBy() and Map.groupBy() for array grouping, Promise.withResolvers() for creating deferred promises, ArrayBuffer.prototype.transfer() and resizable ArrayBuffers, String.isWellFormed() and String.toWellFormed() for Unicode validation, Atomics.waitAsync() for asynchronous atomic operations, and the RegExp v flag for set notation and Unicode string properties.
What is the difference between Object.groupBy() and Map.groupBy()?
Object.groupBy() returns a null-prototype plain object where each key is a group name (coerced to string) and each value is an array of grouped items. Map.groupBy() returns a Map instance, which is useful when your grouping keys are not strings (e.g., objects, symbols, or other non-primitive values) or when you need Map methods like .has() and .forEach(). Both accept an iterable and a callback that returns the grouping key.
When will the Temporal API be available in JavaScript?
The Temporal API is at TC39 Stage 3 and is expected to ship in browsers progressively. It replaces the problematic Date object with types like Temporal.PlainDate, Temporal.ZonedDateTime, and Temporal.Duration for correct date/time arithmetic, time zone handling, and calendar support. You can use the @js-temporal/polyfill package today to start using it in production.
What are Iterator Helpers in JavaScript ES2025?
Iterator Helpers add built-in methods like .map(), .filter(), .take(), .drop(), .forEach(), .toArray(), .find(), .some(), .every(), and .reduce() directly to iterator objects. This lets you chain lazy transformations on any iterable (generators, Map entries, Set values) without converting to an array first. They are part of ES2025 and are already shipping in Chrome, Firefox, and Safari.