Skip to content

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.
npx tsx nouveautes/groupBy-et-fromAsync.ts

Résultat de l'exécution :

avec reduce : {
  admis: [
    { nom: 'Ana', note: 15 },
    { nom: 'Nora', note: 12 },
    { nom: 'Zoé', note: 17 }
  ],
  'recalé': [ { nom: 'Léo', note: 8 }, { nom: 'Théo', note: 6 } ]
}
avec Object.groupBy : [Object: null prototype] {
  admis: [
    { nom: 'Ana', note: 15 },
    { nom: 'Nora', note: 12 },
    { nom: 'Zoé', note: 17 }
  ],
  'recalé': [ { nom: 'Léo', note: 8 }, { nom: 'Théo', note: 6 } ]
}
admis = [
  { nom: 'Ana', note: 15 },
  { nom: 'Nora', note: 12 },
  { nom: 'Zoé', note: 17 }
]
recalés = [ { nom: 'Léo', note: 8 }, { nom: 'Théo', note: 6 } ]
avec Map.groupBy, clé [true] (admis) = [
  { nom: 'Ana', note: 15 },
  { nom: 'Nora', note: 12 },
  { nom: 'Zoé', note: 17 }
]
avec Map.groupBy, clé [false] (recalés) = [ { nom: 'Léo', note: 8 }, { nom: 'Théo', note: 6 } ]
--- Array.fromAsync ---
pages = [ 'page 1', 'page 2', 'page 3' ]

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.