16. Nouveautés ECMAScript 2020-2024
Les chapitres précédents ont déjà présenté, au fil de l'eau, la plupart des nouveautés du langage apparues depuis 2019 — chaînage optionnel et coalescence des nuls (bases-09), Object.hasOwn et structuredClone (obj-09), nouvelles méthodes de tableaux (tab-05), replaceAll/matchAll (str-07), champs privés natifs (class-05), Error avec cause (excep-04), top-level await (import-04), et les compléments à Promise (async-07).
Ce chapitre réunit les deux dernières nouveautés d'ECMAScript 2024 qui ne rentraient dans aucun chapitre existant : [Object.groupBy]/[Map.groupBy], et [Array.fromAsync]. Le script se trouve dans le dossier [nouveautes] du projet.
16.1. script [groupBy-et-fromAsync]
'use strict';
// ========================================================================
// [NOUVEAU depuis 2019] Object.groupBy, Map.groupBy, Array.fromAsync (ECMAScript 2024)
// ========================================================================
// ------------------------------------------------------------------------
// 1) Object.groupBy(tableau, fonctionDeGroupage)
// ------------------------------------------------------------------------
// avant, pour regrouper les éléments d'un tableau selon un critère, il fallait
// écrire soi-même une boucle avec reduce() (cf. tableaux/tab-04.js pour reduce)
interface Étudiant {
nom: string;
note: number;
}
const étudiants: Étudiant[] = [
{ nom: "Ana", note: 15 },
{ nom: "Léo", note: 8 },
{ nom: "Nora", note: 12 },
{ nom: "Théo", note: 6 },
{ nom: "Zoé", note: 17 }
];
// ancienne façon, avec reduce()
const groupesAvecReduce = étudiants.reduce((accumulateur: Record<string, Étudiant[]>, étudiant) => {
const catégorie = étudiant.note >= 10 ? "admis" : "recalé";
// s'il n'existe pas encore de tableau pour cette catégorie, on le crée
(accumulateur[catégorie] ??= []).push(étudiant);
return accumulateur;
}, {});
console.log("avec reduce :", groupesAvecReduce);
// nouvelle façon, bien plus lisible, avec Object.groupBy
const groupes = Object.groupBy(étudiants, étudiant => (étudiant.note >= 10 ? "admis" : "recalé"));
console.log("avec Object.groupBy :", groupes);
console.log("admis =", groupes.admis);
console.log("recalés =", groupes.recalé);
// Map.groupBy fonctionne pareil, mais rend une [Map] plutôt qu'un objet littéral
// utile quand les clés de regroupement ne sont pas des chaînes (ex: des objets, des nombres)
const groupesEnMap = Map.groupBy(étudiants, étudiant => étudiant.note >= 10);
console.log("avec Map.groupBy, clé [true] (admis) =", groupesEnMap.get(true));
console.log("avec Map.groupBy, clé [false] (recalés) =", groupesEnMap.get(false));
// ------------------------------------------------------------------------
// 2) Array.fromAsync(itérableAsynchrone)
// ------------------------------------------------------------------------
// Array.from() (ES2015) sait construire un tableau à partir d'un itérable synchrone
// Array.fromAsync() sait faire la même chose à partir d'un itérable ASYNCHRONE,
// c'est-à-dire une source qui produit ses éléments un par un, au fil du temps
// (par exemple : lecture d'un flux réseau page par page)
// un générateur asynchrone qui produit 3 valeurs, avec un petit délai entre chacune
async function* générateurDePages(): AsyncGenerator<string> {
for (let i = 1; i <= 3; i++) {
// on simule une attente réseau avant chaque page
await new Promise(resolve => setTimeout(resolve, 100));
yield `page ${i}`;
}
}
async function démoFromAsync(): Promise<void> {
console.log("--- Array.fromAsync ---");
// sans Array.fromAsync, il aurait fallu écrire une boucle "for await...of" manuelle
const pages = await Array.fromAsync(générateurDePages());
console.log("pages =", pages);
}
démoFromAsync();
- ligne 33 : [Object.groupBy(tableau, fonction)] regroupe les éléments d'un tableau selon le résultat d'une fonction de classement — bien plus lisible que la construction manuelle par reduce() (comparez les deux constructions, lignes 24-29 puis ligne 33) ;
- le résultat de Object.groupBy est un objet sans prototype ([Object: null prototype]) — un détail visible à l'exécution, qui évite certains pièges liés aux propriétés héritées de Object.prototype (comme toString, hasOwnProperty...) si jamais une catégorie portait ce nom ;
- ligne 40 : [Map.groupBy] fonctionne de façon identique, mais rend une Map plutôt qu'un objet littéral — indispensable dès que les clés de regroupement ne sont pas des chaînes de caractères (ici, des booléens) ;
- [Array.fromAsync(itérableAsynchrone)] construit un tableau à partir d'une source asynchrone — un générateur async function*, par exemple — sans avoir à écrire soi-même une boucle for await...of manuelle pour accumuler les résultats.
Résultat de l'exécution :
16.2. Tableau récapitulatif
Récapitulons, par version d'ECMAScript, l'ensemble des nouveautés présentées dans ce cours depuis le chapitre « Les bases » :
- [ES2020] : chaînage optionnel ?., coalescence des nuls ??, BigInt, Promise.allSettled, String.matchAll ;
- [ES2021] : affectation logique ||=/&&=/??=, séparateurs numériques 1_000_000, Promise.any, String.replaceAll ;
- [ES2022] : champs et méthodes privés #nom, blocs d'initialisation statiques, Object.hasOwn, Array.at, top-level await, Error avec cause ;
- [ES2023] : findLast/findLastIndex, méthodes immuables toSorted/toReversed/toSpliced/with ;
- [ES2024] : Object.groupBy/Map.groupBy, Array.fromAsync, Promise.withResolvers, String.isWellFormed/toWellFormed.
Les chapitres suivants présentent les nouveautés d'ECMAScript 2026, ainsi que [Temporal], le remplaçant moderne de l'objet Date.