Cours JavaScript et TypeScript · Module 5: JavaScript avancé
ES2024 et ES2025 - Les dernières fonctionnalités de JavaScript
Dans cette leçon5
Le Dr Henry Wu vient de rentrer d'une conférence de développeurs. "JavaScript évolue sans cesse", dit-il. "Les dernières versions d'ECMAScript apportent des fonctionnalités qui vont considérablement simplifier notre code !" Jusqu'ici, regrouper des éléments exigeait reduce() et un objet vide, et trier une liste sans détruire l'original demandait une copie manuelle. Les nouvelles versions du standard transforment ces rustines en une seule ligne.
Comment évolue ECMAScript
Le langage est développé par le comité TC39, et depuis 2015 une nouvelle version du standard paraît chaque année, nommée d'après son année de publication. Une proposition franchit des étapes successives et entre dans le standard une fois l'étape 4 atteinte, qui exige notamment des implémentations fonctionnelles. Les grandes étapes de cette évolution :
- ES6 (ES2015) -
letetconst, fonctions fléchées, déstructuration - ES2017 -
asyncetawait - ES2020 - optional chaining (
?.) et nullish coalescing (??) - ES2023 - méthodes de tableaux sans mutation, p. ex.
toSorted() - ES2024 -
Object.groupBy()etPromise.withResolvers() - ES2025 - nouvelles méthodes de
Set,Promise.try()et import de JSON
ES2024 - Les nouveautés les plus importantes
1. Object.groupBy() et Map.groupBy()
Regrouper les éléments d'un tableau selon une clé - sans bibliothèque externe ! Tu fournis le tableau et une fonction qui renvoie, pour chaque dinosaure, le nom de son groupe :
1const dinosaurs = [
2 { name: 'Rex', type: 'carnivore', sector: 7 },
3 { name: 'Blue', type: 'carnivore', sector: 3 },
4 { name: 'Sarah', type: 'herbivore', sector: 5 },
5 { name: 'Bronty', type: 'herbivore', sector: 5 },
6 { name: 'Delta', type: 'carnivore', sector: 3 }
7];
8
9// Regroupement par type
10const byType = Object.groupBy(dinosaurs, dino => dino.type);
11console.log(byType);
12// {
13// carnivore: [{ name: 'Rex', ... }, { name: 'Blue', ... }, { name: 'Delta', ... }],
14// herbivore: [{ name: 'Sarah', ... }, { name: 'Bronty', ... }]
15// }
16
17// Regroupement par secteur
18const bySector = Object.groupBy(dinosaurs, dino => `Sector-${dino.sector}`);
19console.log(bySector);
20
21// Map.groupBy - quand tu as besoin d'une Map au lieu d'un objet
22const byTypeMap = Map.groupBy(dinosaurs, dino => dino.type);
23console.log(byTypeMap.get('carnivore'));Chaque clé du résultat mène à un tableau des dinosaures correspondants - c'est ce que nous construisions auparavant avec reduce(). L'objet renvoyé par Object.groupBy() n'a pas de prototype, il n'héritera donc pas de méthodes comme hasOwnProperty(). Choisis Map.groupBy() quand les clés doivent être des objets, et non des chaînes.
2. Les méthodes de tableaux sans mutation (ES2023)
Formellement, ces méthodes sont entrées dans le standard un an plus tôt, avec ES2023, mais elles font toujours partie des outils les plus récents. Elles renvoient un nouveau tableau au lieu de modifier l'original :
1const sectors = ['Sector-1', 'Sector-7', 'Sector-3', 'Sector-5'];
2
3// Les anciennes méthodes MODIFIENT le tableau d'origine
4const oldWay = [...sectors];
5oldWay.sort(); // Modifie oldWay !
6
7// toSorted() - trie sans mutation
8const sorted = sectors.toSorted();
9console.log(sorted); // ['Sector-1', 'Sector-3', 'Sector-5', 'Sector-7']
10console.log(sectors); // ['Sector-1', 'Sector-7', 'Sector-3', 'Sector-5'] - inchangé !
11
12// toReversed() - inverse sans mutation
13const reversed = sectors.toReversed();
14console.log(reversed); // ['Sector-5', 'Sector-3', 'Sector-7', 'Sector-1']
15console.log(sectors); // Toujours l'original !
16
17// toSpliced() - splice sans mutation
18const withNew = sectors.toSpliced(1, 1, 'Sector-9', 'Sector-10');
19console.log(withNew); // ['Sector-1', 'Sector-9', 'Sector-10', 'Sector-3', 'Sector-5']
20
21// with() - remplacer un élément sans mutation
22const updated = sectors.with(1, 'Sector-ALPHA');
23console.log(updated); // ['Sector-1', 'Sector-ALPHA', 'Sector-3', 'Sector-5']
24
25// Exemple pratique : trier des dinosaures
26const dinoList = [
27 { name: 'Rex', dangerLevel: 10 },
28 { name: 'Blue', dangerLevel: 8 },
29 { name: 'Sarah', dangerLevel: 3 }
30];
31
32// Trier par niveau de danger (décroissant) sans mutation
33const byDanger = dinoList.toSorted((a, b) => b.dangerLevel - a.dangerLevel);
34console.log(byDanger[0].name); // 'Rex'Chaque nouvelle méthode a une grande sœur mutatrice : toSorted() correspond à sort(), toReversed() à reverse(), et toSpliced() à splice(). with(index, value) remplace l'affectation par indice et lève un RangeError pour un indice hors du tableau. Après toutes ces opérations, la liste sectors est exactement la même qu'au début.
3. Promise.withResolvers()
Créer plus facilement une Promise avec un resolve/reject externe. C'est utile quand la promesse doit être résolue depuis un autre endroit que son constructeur :
1// L'ancienne façon - peu pratique
2let externalResolve, externalReject;
3const oldPromise = new Promise((resolve, reject) => {
4 externalResolve = resolve;
5 externalReject = reject;
6});
7
8// La nouvelle façon avec Promise.withResolvers()
9const { promise, resolve, reject } = Promise.withResolvers();
10
11// Maintenant, on peut appeler resolve/reject depuis n'importe où
12setTimeout(() => {
13 resolve('Opération terminée !');
14}, 1000);
15
16promise.then(console.log); // "Opération terminée !" après 1 secondeL'ancienne façon exige des variables déclarées avant le constructeur, alors que Promise.withResolvers() renvoie directement un objet avec trois champs, que l'on décompose par déstructuration. On le voit le mieux avec un capteur qui promet une mesure maintenant et la fournit plus tard :
1// Utile pour les opérations asynchrones pilotées de l'extérieur
2class DinosaurSensor {
3 constructor(id) {
4 this.id = id;
5 const { promise, resolve, reject } = Promise.withResolvers();
6 this.readingPromise = promise;
7 this.resolveReading = resolve;
8 this.rejectReading = reject;
9 }
10
11 receiveReading(data) {
12 this.resolveReading(data);
13 }
14
15 sensorFailure(error) {
16 this.rejectReading(error);
17 }
18}
19
20const sensor = new DinosaurSensor("TREX-001");
21
22// On définit ce qui se passera à l'arrivée de la mesure
23sensor.readingPromise.then(reading => {
24 console.log(`Capteur ${sensor.id} : ${JSON.stringify(reading)}`);
25});
26
27// Plus tard, quand la mesure arrive :
28sensor.receiveReading({ temperature: 38.5, heartRate: 72 });Le constructeur crée la promesse et met de côté resolve et reject pour plus tard. Le code qui attend la mesure se branche via then(), et receiveReading() ne tient la promesse qu'au moment où les données arrivent.
ES2025 - Les fonctionnalités les plus récentes
1. Les nouvelles méthodes de Set
Les opérations sur les ensembles - enfin intégrées à JavaScript ! Chaque méthode prend un second ensemble et renvoie un nouveau Set ou une valeur logique :
1const carnivores = new Set(['Rex', 'Blue', 'Delta', 'Echo']);
2const sectorA = new Set(['Rex', 'Sarah', 'Delta']);
3
4// intersection() - éléments communs
5const dangerousInA = carnivores.intersection(sectorA);
6console.log([...dangerousInA]); // ['Rex', 'Delta']
7
8// difference() - éléments présents uniquement dans le premier ensemble
9const safeInA = sectorA.difference(carnivores);
10console.log([...safeInA]); // ['Sarah']
11
12// union() - tous les éléments des deux ensembles
13const allKnown = carnivores.union(sectorA);
14console.log([...allKnown]); // ['Rex', 'Blue', 'Delta', 'Echo', 'Sarah']
15
16// symmetricDifference() - éléments présents dans un seul des ensembles
17const exclusive = carnivores.symmetricDifference(sectorA);
18console.log([...exclusive]); // ['Blue', 'Echo', 'Sarah']
19
20// isSubsetOf() - est-ce un sous-ensemble ?
21const smallGroup = new Set(['Rex', 'Blue']);
22console.log(smallGroup.isSubsetOf(carnivores)); // true
23
24// isSupersetOf() - contient-il tous les éléments ?
25console.log(carnivores.isSupersetOf(smallGroup)); // true
26
27// isDisjointFrom() - aucun élément en commun ?
28const herbivores = new Set(['Sarah', 'Bronty', 'Tricy']);
29console.log(carnivores.isDisjointFrom(herbivores)); // trueLes quatre premières méthodes renvoient un nouveau Set, et les ensembles d'origine restent intacts. Les méthodes préfixées par is répondent seulement true ou false, et le spread transforme le résultat en tableau, car Set est itérable.
Puisqu'on parle d'ensembles : la structure de données détermine la vitesse. L'accès par indice (array[0]) ou à une propriété d'objet (obj.key) a un coût constant, noté O(1), indépendant du nombre d'éléments. indexOf() et includes() parcourent dans le pire des cas tout le tableau, soit O(n), et un tri coûte généralement O(n log n). Set.has() fonctionne en pratique en temps constant, donc quand on vérifie souvent la présence d'un élément, l'ensemble l'emporte sur le tableau.
2. Promise.try()
Exécuter une fonction en toute sécurité dans le contexte d'une Promise. Une fonction peut échouer de deux façons : lever une erreur immédiatement ou renvoyer une promesse rejetée, et sans Promise.try() il faut gérer les deux chemins séparément :
1// Sans Promise.try - on gère séparément les erreurs synchrones et asynchrones
2function getDinosaurStatus(id) {
3 return new Promise((resolve, reject) => {
4 try {
5 const result = syncOperation(id); // peut lever une erreur immédiatement
6 resolve(asyncOperation(result)); // peut renvoyer une promesse rejetée
7 } catch (e) {
8 reject(e);
9 }
10 });
11}
12
13// Avec Promise.try - plus lisible
14function getDinosaurStatus(id) {
15 return Promise.try(() => {
16 const result = syncOperation(id); // l'erreur synchrone sera interceptée
17 return asyncOperation(result); // le rejet aussi
18 });
19}
20
21// En pratique : exécuter n'importe quelle fonction comme une promesse, en sécurité
22const safeCheck = Promise.try(() => {
23 validateDinoId("RAPTOR-001"); // peut lever une erreur
24 return fetchDinosaurData("RAPTOR-001"); // renvoie une promesse
25});
26
27safeCheck
28 .then(data => console.log("Données :", data))
29 .catch(err => console.error("Erreur :", err));Promise.try() transforme une erreur levée de façon synchrone en promesse rejetée, un seul .catch() intercepte donc les deux types de pannes. Un petit test avec une fonction qui lève une erreur sur demande :
1function riskyOperation(shouldThrow) {
2 if (shouldThrow) {
3 throw new Error('Erreur synchrone !');
4 }
5 return 'Succès';
6}
7
8// La nouvelle façon avec Promise.try()
9const result = Promise.try(() => riskyOperation(false));
10result.then(console.log); // 'Succès'
11
12const error = Promise.try(() => riskyOperation(true));
13error.catch(err => console.error(err.message)); // 'Erreur synchrone !'Les deux résultats aboutissent dans de simples then() et catch() - l'erreur synchrone n'a pas interrompu le programme, elle est devenue un rejet.
3. Les Import Attributes (modules JSON)
Importer du JSON directement. L'attribut with { type: 'json' } indique à l'environnement quel type de fichier on charge :
1// Importer un fichier JSON avec un attribut de type
2import dinoConfig from './dinosaurs.json' with { type: 'json' };
3
4console.log(dinoConfig.species);
5
6// Import dynamique avec attributs
7const config = await import('./config.json', { with: { type: 'json' } });L'attribut est une protection : si le serveur renvoie un script au lieu du JSON, l'import échoue au lieu d'exécuter du code étranger. Cette syntaxe fonctionne dans les modules ES, que tu as découverts plus tôt dans cette location.
Vérifier la prise en charge par le navigateur
Les nouvelles fonctionnalités n'arrivent pas dans tous les navigateurs en même temps. Avant de t'en servir, vérifie qu'elles existent, en comparant le type avec la chaîne 'function' :
1const es2024Support = {
2 objectGroupBy: typeof Object.groupBy === 'function',
3 arrayToSorted: typeof [].toSorted === 'function', // formellement ES2023
4 arrayToReversed: typeof [].toReversed === 'function', // formellement ES2023
5 arrayWith: typeof [].with === 'function', // formellement ES2023
6 promiseWithResolvers: typeof Promise.withResolvers === 'function'
7};
8
9const es2025Support = {
10 setIntersection: typeof Set.prototype.intersection === 'function',
11 setDifference: typeof Set.prototype.difference === 'function',
12 promiseTry: typeof Promise.try === 'function'
13};
14
15console.log('ES2024 Support:', es2024Support);
16console.log('ES2025 Support:', es2025Support);Cette détection de fonctionnalités (feature detection) est plus fiable que de deviner d'après le nom du navigateur. Quand une fonctionnalité manque, ajoute un polyfill, c'est-à-dire un script qui ajoute la méthode manquante.
Résumé
ES2024 et ES2025 apportent :
- Object.groupBy() / Map.groupBy() - regrouper des données sans lodash et sans reduce()
- toSorted(), toReversed(), toSpliced(), with() - l'immutabilité sans opérateur spread (formellement depuis ES2023)
- Promise.withResolvers() - une gestion plus simple des Promise
- Les méthodes de Set - les opérations sur les ensembles intégrées au langage
- Promise.try() - une gestion uniforme des erreurs synchrones et asynchrones
- Les Import Attributes - la déclaration explicite du type d'un module importé
N'oubliez pas de vérifier la compatibilité des navigateurs et d'utiliser des polyfills si nécessaire ! Mon conseil : utilise sans hésiter toSorted() et Object.groupBy(), mais pour les anciens navigateurs, consulte le tableau de compatibilité sur MDN. Dans la prochaine leçon, tu découvriras les observateurs intégrés au navigateur.
Retiens bien : JavaScript évolue comme les espèces du laboratoire - chaque année apporte de nouvelles fonctionnalités, mais avant de les lâcher dans l'enclos, vérifie que le navigateur les reconnaît.
Tu as repéré une erreur dans cette leçon ?
Teste tes connaissances
Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.
1. Que fait la méthode Object.groupBy() introduite dans ES2024 ?
2. En quoi toSorted() diffère-t-elle de sort() en JavaScript ?
Voici 2 des 3 questions de cette leçon. Réponds aux autres dans le jeu.
Tâches pratiques dans le jeu
- Éditeur de code
Regroupe les dinosaures par type, trie-les par niveau de menace et remplace des éléments sans mutation.
- Tri vertical
Ordonne chronologiquement les fonctionnalités de JavaScript par standard ECMAScript :