ES2024 (ECMAScript 2024) ships three utility methods that each solve a common boilerplate pattern: Object.groupBy() replaces Array.reduce()-based grouping, Promise.withResolvers() replaces the resolver-capture pattern, and Array.fromAsync() replaces manual async iterator consumption. All three are baseline in Chrome 117+, Firefox 119+, and Safari 17.2+.
Problem: JavaScript code in WordPress admin tools groups arrays of posts, filters sets of users, or creates deferred promises — patterns that require verbose reduce() callbacks or external utility libraries like lodash.
Solution: ES2024 adds Object.groupBy(array, fn) and Map.groupBy(array, fn) for one-line grouping, and Promise.withResolvers() to expose a promise's resolve/reject functions outside the constructor. These replace common lodash patterns with native equivalents available in all modern browsers and Node.js 21+.
The examples below show all three methods, compare them with their pre-ES2024 equivalents, and apply them to practical WordPress admin scripting scenarios like grouping posts by status and paginating an async REST API generator.
// ── 1. Object.groupBy() ───────────────────────────────────────────────────
// Before ES2024: verbose reduce
const byStatus = posts.reduce( ( acc, post ) => {
( acc[ post.status ] ??= [] ).push( post );
return acc;
}, {} );
// ES2024: one call
const grouped = Object.groupBy( posts, post => post.status );
// { publish: [...], draft: [...], pending: [...] }
// Group WordPress REST API posts by category
const resp = await fetch( '/wp-json/wp/v2/posts?per_page=100&_fields=id,title,categories' );
const posts2 = await resp.json();
const byCat = Object.groupBy( posts2, post =>
post.categories[0] ?? 'uncategorised'
);
console.log( byCat );
// Map.groupBy() — same API but returns a Map (keys can be any value)
const byLength = Map.groupBy( posts2, p => p.title.rendered.length > 60 ? 'long' : 'short' );
// ── 2. Promise.withResolvers() ────────────────────────────────────────────
// Before: resolver capture pattern (ugly)
let resolve, reject;
const promise = new Promise( ( res, rej ) => { resolve = res; reject = rej; } );
// ES2024: clean destructuring
const { promise: uploadDone, resolve: onUpload, reject: onFail } = Promise.withResolvers();
// Practical use: resolve a promise from an event handler
function waitForMediaUpload( frameId ) {
const { promise, resolve, reject } = Promise.withResolvers();
const frame = wp.media.frames[ frameId ];
frame.on( 'select', () => resolve( frame.state().get( 'selection' ).toJSON() ) );
frame.on( 'close', () => reject( new Error( 'Media frame closed' ) ) );
return promise;
}
const media = await waitForMediaUpload( 'featured-image' );
console.log( 'Selected:', media );
// ── 3. Array.fromAsync() ──────────────────────────────────────────────────
// Before: manual async iterator
async function collectAsyncIter( iter ) {
const results = [];
for await ( const item of iter ) results.push( item );
return results;
}
// ES2024:
const allItems = await Array.fromAsync( asyncGenerator() );
// Practical: paginate WordPress REST API and collect all posts
async function* fetchAllPosts( type = 'post' ) {
let page = 1;
while ( true ) {
const resp = await fetch( `/wp-json/wp/v2/${type}?per_page=100&page=${page}&_fields=id,title,slug` );
if ( ! resp.ok ) break;
const posts = await resp.json();
if ( ! posts.length ) break;
yield* posts;
if ( posts.length < 100 ) break;
page++;
}
}
// Collect every published post into one array:
const allPosts = await Array.fromAsync( fetchAllPosts( 'post' ) );
console.log( `Total posts: ${allPosts.length}` );
// With a mapping function (second argument):
const slugs = await Array.fromAsync( fetchAllPosts( 'page' ), p => p.slug );
console.log( slugs );
NOTE: Object.groupBy() creates an object with a null prototype — it does not inherit from Object.prototype, so methods like .hasOwnProperty() and toString() are not available on the result; if you need those methods, wrap the result: Object.assign(Object.create(null), grouped) or use Map.groupBy() which returns a standard Map.