Avez-vous une question? (216) 97 656 803 hajjriadh@gmail.com
Angular

Rappel des normes ECMAScript (ES6 et suivantes)

Cette leçon fait le point sur ECMAScript, la norme qui définit JavaScript : la différence entre la norme et le langage, le rôle d'Ecma International et du comité TC39, et pourquoi ce rappel est indisp...

0 chapitres 0 QCM Accès gratuit
Dans cet article

À propos de ce cours

Cette leçon fait le point sur ECMAScript, la norme qui définit JavaScript : la différence entre la norme et le langage, le rôle d'Ecma International et du comité TC39, et pourquoi ce rappel est indispensable avant Angular, puisque TypeScript ajoute seulement les types et les décorateurs à ECMAScript. Un tableau et une frise chronologique présentent les versions d'ES5 à ES2026, approuvée le 30 juin 2026. Chaque nouveauté est illustrée par un exemple : let et const, fonctions fléchées et this, gabarits ${}, déstructuration, spread et rest, paramètres par défaut, classes (extends, super, static, champs privés #), modules import / export, méthodes de tableaux de map à Object.groupBy, chaînage optionnel ?. et opérateur ??, Map et Set, promesses et async / await. Huit activités pratiques permettent de s'exercer avec Node.js.
  1. Objectifs

    • Savoir ce qu'est ECMAScript et le distinguer de JavaScript
    • Connaître les principales versions de la norme, d'ES5 aux versions annuelles récentes
    • Utiliser les nouveautés d'ES6 et suivantes : let / const, fonctions fléchées, gabarits de chaînes, déstructuration, spread / rest, classes, modules
    • Manipuler les tableaux et les objets avec les méthodes modernes (map, filter, reduce, find, at, toSorted…)
    • Utiliser le chaînage optionnel ?. et l'opérateur ??
    • Écrire du code asynchrone avec les promesses et async / await
    • Reconnaître ces notions dans le code d'une application Angular
  2. Présentation

    • ECMAScript (abrégé ES) est la norme qui définit le langage JavaScript : sa syntaxe, ses types, ses mots-clés et ses objets de base (Array, Object, Promise…). Son nom officiel est ECMA-262.
    • JavaScript est l'implémentation la plus connue de cette norme : les moteurs V8 (Chrome, Edge, Node.js), SpiderMonkey (Firefox) et JavaScriptCore (Safari) appliquent ECMAScript.
    • La norme est publiée par l'organisme Ecma International. Les nouveautés sont proposées et validées par le comité TC39, qui réunit les éditeurs de navigateurs et de grandes entreprises du Web.
    • Une analogie : ECMAScript est la recette, JavaScript est le plat préparé par chaque navigateur en suivant cette recette.
    • Pourquoi ce rappel avant Angular ? Angular s'écrit en TypeScript, qui est un sur-ensemble d'ECMAScript : TypeScript ajoute les types et les décorateurs, mais tout le reste (classes, modules, fonctions fléchées, déstructuration, async / await…) est de l'ECMAScript moderne. Sans ces bases, le code Angular est très difficile à lire.

Les principales versions d'ECMAScript

  1. Les versions d'ECMAScript

    • Jusqu'en 2015, les versions étaient numérotées (ES3, ES5…) et espacées de plusieurs années. ES6, sortie en 2015 sous le nom officiel ES2015, a profondément modernisé le langage.
    • Depuis, une nouvelle version sort chaque année, en juin, et porte le nom de son année : ES2016, ES2017… Chaque version ajoute quelques nouveautés. Une proposition passe par plusieurs stades (de 0 à 4) au TC39 avant d'être intégrée à la norme.
    • On parle souvent d'« ES6+ » pour désigner l'ensemble des nouveautés apparues depuis 2015 : c'est le JavaScript moderne.
Version Année Principales nouveautés
ES5 2009 mode strict ('use strict'), forEach, map, filter, reduce, JSON
ES6 / ES2015 2015 let et const, fonctions fléchées, classes, modules, gabarits de chaînes, déstructuration, spread et rest, paramètres par défaut, promesses, Map et Set, for…of
ES2016 2016 Array.prototype.includes(), opérateur puissance **
ES2017 2017 async / await, Object.entries(), Object.values(), padStart()
ES2018 2018 spread et rest sur les objets, Promise.finally()
ES2019 2019 flat(), flatMap(), Object.fromEntries()
ES2020 2020 chaînage optionnel ?., coalescence des nuls ??, BigInt, Promise.allSettled()
ES2021 2021 ??=, ||=, &&=, replaceAll(), séparateur numérique 1_000_000
ES2022 2022 champs et méthodes privés #, champs static, at(), await au niveau supérieur d'un module
ES2023 2023 toSorted(), toReversed(), findLast(): méthodes qui ne modifient pas le tableau d'origine
ES2024 2024 Object.groupBy(), Promise.withResolvers()
ES2025 2025 méthodes des ensembles (union, intersection, difference…), assistants d'itérateurs, Promise.try()
ES2026 2026 Math.sumPrecise(), Error.isError(), Array.fromAsync(), Iterator.concat()…
  • Retenir surtout ES6 (2015) : la très grande majorité des notions utilisées chaque jour dans Angular datent de cette version. Les versions suivantes apportent des améliorations pratiques, dont plusieurs sont présentées dans cette leçon.
  • Préparer l'environnement de test

    • Tous les exemples de cette leçon s'exécutent avec Node.js, sans navigateur (voir Présentation de l'environnement de développement Node.js) :
      • un fichier .js s'exécute avec node nom-du-fichier.js;
      • un fichier qui utilise import / export porte l'extension .mjs (module ES) et s'exécute de la même façon : node nom-du-fichier.mjs.
    • On peut aussi tester une ligne rapidement dans la console du navigateur (F12, onglet Console) ou dans la console interactive de Node (node, puis .exit pour quitter).
  • Activité 1 : préparer le dossier et le dépôt GitHub

    • Créer un dépôt vide rappel-ecmascript sur GitHub (sans README).
    • Créer le dossier du même nom, l'ouvrir dans VS Code et initialiser Git :
    mkdir rappel-ecmascript
    cd rappel-ecmascript
    git init
    code .
    • Créer un fichier 00-test.js contenant console.log('ECMAScript avec Node.js', process.version); et l'exécuter avec node 00-test.js.
    • Faire le premier commit et l'envoyer sur GitHub :
    git add .
    git commit -m "Activité 1 : préparation du dossier"
    git branch -M main
    git remote add origin https://github.com/VOTRE-COMPTE/rappel-ecmascript.git
    git push -u origin main
    • Règle de la leçon : chaque activité est écrite dans un fichier numéroté (02-portee.js, 03-fiche.js…) et se termine par un commit et un push.
  • let et const : déclarer des variables

    • Avant ES6, on déclarait toutes les variables avec var. ES6 introduit let et const, qui corrigent ses défauts.
    • const déclare une variable qu'on ne peut pas réaffecter. C'est le choix par défaut.
    • let déclare une variable qu'on peut réaffecter (un compteur, un total qui évolue…).
    • var est à éviter : sa portée est la fonction entière, pas le bloc { }, ce qui provoque des erreurs difficiles à trouver.
      var let const
    Portée la fonction le bloc { } le bloc { }
    Réaffectation oui oui non
    Utilisation avant la déclaration vaut undefined erreur ReferenceError erreur ReferenceError
    Redéclaration dans la même portée autorisée interdite interdite
    const tauxTVA = 0.19;
    // tauxTVA = 0.07;   // TypeError: Assignment to constant variable.
    
    let total = 0;
    total = total + 50;   // autorisé avec let
    
    // const empêche la réaffectation, pas la modification du contenu
    const stagiaire = { nom: 'Sami' };
    stagiaire.nom = 'Amira';          // autorisé : on modifie une propriété
    console.log(stagiaire);           // { nom: 'Amira' }
    // stagiaire = { nom: 'Karim' };  // interdit : réaffectation
    • Exemple : la portée de var et de let dans une boucle
    for (var i = 0; i < 3; i++) {
      setTimeout(() => console.log('var', i), 0);
    }
    for (let j = 0; j < 3; j++) {
      setTimeout(() => console.log('let', j), 0);
    }
    // Résultat :
    // var 3   var 3   var 3      → une seule variable i, partagée
    // let 0   let 1   let 2      → une nouvelle variable j à chaque tour
    • Règle simple : utilisez const partout ; remplacez-le par let seulement si VS Code ou Node signale que la variable doit être réaffectée. N'utilisez plus var.
  • Activité 2 : prédire avant d'exécuter

    • Dans 02-portee.js, recopier le code ci-dessous. Avant de l'exécuter, écrire en commentaire ce que vous pensez qu'il affiche. Exécuter, puis comparer et expliquer chaque différence en commentaire.
    console.log(a);      // ?
    var a = 3;
    
    try {
      console.log(b);    // ?
      let b = 3;
    } catch (e) {
      console.log(e.name + ' : ' + e.message);
    }
    
    const formation = { titre: 'Web', places: 10 };
    formation.places--;
    console.log(formation.places);   // ?
    
    if (true) {
      var x = 'var';
      let y = 'let';
    }
    console.log(x);                  // ?
    console.log(typeof y);           // ?
    1. Les fonctions fléchées

      • Une fonction fléchée (arrow function) est une écriture courte d'une fonction, avec la flèche =>.
    // Fonction classique
    function carre(n) {
      return n * n;
    }
    
    // Fonction fléchée équivalente
    const carreFlechee = (n) => n * n;       // une seule expression : return implicite
    
    const saluer = () => 'Bonjour';           // aucun paramètre : parenthèses vides
    const addition = (a, b) => a + b;          // plusieurs paramètres
    const moyenne = (notes) => {               // plusieurs instructions : accolades + return
      const somme = notes.reduce((s, n) => s + n, 0);
      return somme / notes.length;
    };
    const creerStagiaire = (nom) => ({ nom, actif: true });   // renvoyer un objet : parenthèses autour
    
    console.log(carreFlechee(4), saluer(), addition(2, 3), moyenne([10, 14, 18]));
    • Différence importante : une fonction fléchée n'a pas son propre this. Elle utilise le this de l'endroit où elle est écrite. C'est très pratique dans les classes Angular, notamment dans les fonctions de rappel (callbacks).
    const compteur = {
      valeur: 0,
      demarrerClassique() {
        setTimeout(function () {
          console.log('classique :', this === compteur);   // false : this a changé
        }, 0);
      },
      demarrerFlechee() {
        setTimeout(() => {
          this.valeur++;                                  // this = compteur
          console.log('fléchée :', this.valeur);           // 1
        }, 0);
      },
    };
    compteur.demarrerClassique();
    compteur.demarrerFlechee();
    1. Les gabarits de chaînes (template literals)

      • Un gabarit s'écrit entre accents graves ` ` (Alt Gr + 7 sur un clavier AZERTY). On y insère des expressions avec ${ }, et il peut s'étendre sur plusieurs lignes.
    const nom = 'Amira';
    const moyenne = 15.456;
    
    // Avant ES6 : concaténation
    console.log('Bonjour ' + nom + ', votre moyenne est ' + moyenne.toFixed(2) + ' / 20.');
    
    // Avec un gabarit
    console.log(`Bonjour ${nom}, votre moyenne est ${moyenne.toFixed(2)} / 20.`);
    
    // Sur plusieurs lignes, avec une expression
    const carte = `
      <div class="card">
        <h5>${nom.toUpperCase()}</h5>
        <p>${moyenne >= 10 ? 'Admis' : 'Ajourné'}</p>
      </div>`;
    console.log(carte);
    • Dans Angular : on retrouve les accents graves dans la propriété template d'un composant (HTML écrit dans le fichier .ts). Attention à ne pas confondre ${ } (JavaScript) et {{ }} (interpolation Angular dans un template).
  • La déstructuration

    • La déstructuration extrait des valeurs d'un objet ou d'un tableau et les range dans des variables, en une seule instruction.
    // Objet
    const formation = { titre: 'Développement web', duree: 24, niveau: 'BTS', lieu: { ville: 'Tunis' } };
    
    const { titre, niveau } = formation;                 // mêmes noms que les propriétés
    const { duree: dureeMois } = formation;              // renommer : dureeMois = 24
    const { salle = 'A1' } = formation;                  // valeur par défaut si absente
    const { lieu: { ville } } = formation;               // objet imbriqué : ville = 'Tunis'
    console.log(titre, niveau, dureeMois, salle, ville);
    
    // Tableau : la position compte
    const modules = ['HTML', 'CSS', 'JS', 'TS', 'Angular'];
    const [premier, , troisieme, ...reste] = modules;    // on saute le 2e élément
    console.log(premier, troisieme, reste);              // HTML JS [ 'TS', 'Angular' ]
    
    // Échanger deux variables
    let a = 1, b = 2;
    [a, b] = [b, a];
    console.log(a, b);                                   // 2 1
    
    // Dans les paramètres d'une fonction
    const presenter = ({ titre, duree }) => `${titre} (${duree} mois)`;
    console.log(presenter(formation));
    1. Spread, rest, paramètres par défaut et raccourcis

      • Les trois points ... ont deux rôles selon l'endroit où ils sont écrits :
        • spread (étalement) : déplie un tableau ou un objet ;
        • rest (reste) : regroupe plusieurs valeurs dans un tableau.
    // Spread sur un tableau
    const notes = [12, 15, 9];
    console.log(Math.max(...notes));            // 15 : comme Math.max(12, 15, 9)
    const copie = [...notes, 18];               // nouveau tableau, l'original ne change pas
    console.log(copie, notes);                  // [ 12, 15, 9, 18 ] [ 12, 15, 9 ]
    
    // Spread sur un objet (ES2018) : copier puis modifier
    const base = { nom: 'Sami', groupe: 'DEV1' };
    const misAJour = { ...base, groupe: 'DEV2', actif: true };
    console.log(misAJour);                      // { nom: 'Sami', groupe: 'DEV2', actif: true }
    
    // Rest dans les paramètres : nombre de valeurs libre
    function moyenneDe(...valeurs) {
      return valeurs.reduce((s, v) => s + v, 0) / valeurs.length;
    }
    console.log(moyenneDe(10, 14, 18));         // 14
    
    // Paramètres par défaut
    function saluer(nom = 'stagiaire', formule = 'Bonjour') {
      return `${formule} ${nom}`;
    }
    console.log(saluer(), saluer('Amira'), saluer(undefined, 'Salut'));
    
    // Raccourcis d'objets : propriété et méthode
    const groupe = 'DEV1', effectif = 24;
    const classe = {
      groupe,                         // au lieu de groupe: groupe
      effectif,
      afficher() {                    // au lieu de afficher: function () { … }
        return `${this.groupe} (${this.effectif})`;
      },
    };
    console.log(classe.afficher());
    • Dans Angular : on écrit this.produits = [...this.produits, nouveau]; plutôt que this.produits.push(nouveau);. Créer un nouveau tableau permet à Angular de détecter le changement de façon fiable : c'est le principe d'immutabilité.
  • Activité 3 : la fiche du stagiaire

    • Dans 03-fiche.js, écrire une fonction fléchée creerFiche qui :
      • reçoit un objet stagiaire et le déstructure dans ses paramètres (nom, prenom, groupe avec la valeur par défaut 'Non affecté', notes avec la valeur par défaut []) ;
      • reçoit ensuite un nombre libre d'options grâce au paramètre rest;
      • calcule la moyenne (0 si aucune note) et renvoie la fiche avec un gabarit, le nom en majuscules.
    • Tester avec le code suivant ; le résultat doit être exactement celui indiqué :
    console.log(creerFiche({ nom: 'Ben Ali', prenom: 'Amira', groupe: 'DEV1', notes: [14, 16, 17] }));
    console.log(creerFiche({ nom: 'Trabelsi', prenom: 'Sami' }, 'boursier', 'délégué'));
    
    // Résultat attendu :
    // Amira BEN ALI – DEV1 – moyenne : 15.67
    // Sami TRABELSI – Non affecté – moyenne : 0.00 – boursier, délégué
    1. Les classes

      • ES6 introduit le mot-clé class pour écrire des objets « modèles » de façon claire. Toutes les briques Angular sont des classes : composants, services, pipes, directives.
    class Personne {
      static nombre = 0;              // champ statique (ES2022) : partagé par la classe
      #cin;                           // champ privé (ES2022) : invisible hors de la classe
    
      constructor(nom, cin) {         // appelé par new
        this.nom = nom;
        this.#cin = cin;
        Personne.nombre++;
      }
    
      get initiale() {                // accesseur : s'utilise comme une propriété
        return this.nom[0];
      }
    
      presenter() {                   // méthode
        return `Je suis ${this.nom}`;
      }
    }
    
    class Stagiaire extends Personne {     // héritage
      constructor(nom, cin, groupe) {
        super(nom, cin);                   // appelle le constructeur parent
        this.groupe = groupe;
      }
    
      presenter() {                        // redéfinition
        return `${super.presenter()}, stagiaire en ${this.groupe}`;
      }
    }
    
    const s = new Stagiaire('Amira', '01234567', 'DEV1');
    console.log(s.presenter());       // Je suis Amira, stagiaire en DEV1
    console.log(s.initiale);          // A
    console.log(Personne.nombre);     // 1
    console.log(s.cin);               // undefined : #cin est privé
    • Remarque : en TypeScript, on rencontre aussi le mot-clé private (vérifié seulement à la compilation), alors que # est une vraie protection d'ECMAScript, vérifiée à l'exécution.
  • Activité 4 : les classes Formation et FormationEnLigne

    • Dans 04-classes.js, écrire :
      • une classe Formation avec un champ statique nombre, un champ privé #places, un constructeur (titre, dureeMois, places = 20), un accesseur placesRestantes, une méthode inscrire() qui diminue les places ou renvoie « complet », et une méthode presenter();
      • une classe FormationEnLigne qui hérite de Formation, fixe toujours 100 places, ajoute une propriété plateforme et redéfinit presenter() avec super.
    • Tester avec le code suivant :
    const web = new Formation('Développement web', 24, 1);
    const design = new FormationEnLigne('Infographie', 12, 'Moodle');
    console.log(web.presenter());
    console.log(design.presenter());
    console.log(web.inscrire());
    console.log(web.inscrire());
    console.log(`Nombre de formations : ${Formation.nombre}`);
    web.placesRestantes = 50;          // tentative de modification
    console.log(web.placesRestantes);
    
    // Résultat attendu :
    // Développement web – 24 mois
    // Infographie – 12 mois – en ligne sur Moodle
    // Développement web : inscription validée (0 places restantes)
    // Développement web : complet
    // Nombre de formations : 2
    // 0
    • Expliquer en commentaire pourquoi la dernière ligne affiche 0 et non 50.
  • Les modules : import et export

    • Un module est un fichier qui exporte ce qu'il veut partager ; les autres fichiers l'importent. Chaque module a sa propre portée : ses variables ne « fuient » pas dans les autres fichiers.
    • Il existe deux types d'export :
      • export nommé: plusieurs par fichier, importés entre accolades avec le même nom ;
      • export par défaut: un seul par fichier, importé sans accolades avec le nom de son choix.
    // outils.mjs
    export const TVA = 0.19;
    
    export function prixTTC(prixHT) {
      return +(prixHT * (1 + TVA)).toFixed(3);
    }
    
    export default class Panier {
      #articles = [];
      ajouter(article) {
        this.#articles.push(article);
      }
      get total() {
        return this.#articles.reduce((t, a) => t + prixTTC(a.prix), 0);
      }
    }
    // index.mjs
    import Panier, { prixTTC, TVA as tauxTVA } from './outils.mjs';   // défaut + nommés (dont un renommé)
    import * as outils from './outils.mjs';                           // tout le module dans un objet
    
    console.log(prixTTC(100), tauxTVA, outils.TVA);    // 119 0.19 0.19
    
    const panier = new Panier();
    panier.ajouter({ nom: 'Clavier', prix: 45 });
    panier.ajouter({ nom: 'Souris', prix: 20 });
    console.log(`Total TTC : ${panier.total.toFixed(3)} DT`);   // Total TTC : 77.350 DT
    • Dans Angular : le projet est un ensemble de modules ES. Chaque composant est une classe exportée (export class Entete) puis importée là où on l'utilise (import { Entete } from './layout/entete/entete';). Les paquets installés avec npm s'importent par leur nom, sans chemin : import { Component } from '@angular/core';. Angular utilise presque toujours des exports nommés.
  • Les méthodes modernes des tableaux et des objets

    • Ces méthodes remplacent la plupart des boucles for. Elles reçoivent une fonction (souvent fléchée) appelée pour chaque élément. Exemple avec la liste suivante :
    const stagiaires = [
      { nom: 'Amira', groupe: 'DEV1', moyenne: 15.5 },
      { nom: 'Sami', groupe: 'DEV2', moyenne: 9.75 },
      { nom: 'Yasmine', groupe: 'DEV1', moyenne: 12 },
      { nom: 'Karim', groupe: 'DEV2', moyenne: 17.25 },
    ];
    Méthode Rôle Exemple Résultat
    map() transforme chaque élément → nouveau tableau stagiaires.map(s => s.nom) ['Amira', 'Sami', 'Yasmine', 'Karim']
    filter() garde les éléments qui vérifient la condition stagiaires.filter(s => s.moyenne >= 10).length 3
    find() renvoie le premier élément trouvé (ou undefined) stagiaires.find(s => s.nom === 'Sami').moyenne 9.75
    some() / every() au moins un / tous vérifient la condition stagiaires.some(s => s.moyenne < 10) true
    reduce() réduit le tableau à une seule valeur stagiaires.reduce((t, s) => t + s.moyenne, 0) 54.5
    includes() le tableau contient-il cette valeur ? ['HTML', 'CSS'].includes('CSS') true
    at() élément à une position, négative depuis la fin stagiaires.at(-1).nom 'Karim'
    toSorted() copie triée, l'original ne change pas (≠ sort()) stagiaires.toSorted((a, b) => b.moyenne - a.moyenne)[0].nom 'Karim'
    Object.groupBy() regroupe les éléments selon une clé Object.groupBy(stagiaires, s => s.groupe).DEV1.length 2
    Object.entries() paires [clé, valeur] d'un objet Object.entries({ nom: 'Sami', groupe: 'DEV1' }) [['nom', 'Sami'], ['groupe', 'DEV1']]
    // Enchaîner les méthodes : noms des admis, du meilleur au moins bon
    const admis = stagiaires
      .filter((s) => s.moyenne >= 10)
      .toSorted((a, b) => b.moyenne - a.moyenne)
      .map((s) => `${s.nom} (${s.moyenne})`);
    console.log(admis);   // [ 'Karim (17.25)', 'Amira (15.5)', 'Yasmine (12)' ]
    
    // Parcourir un objet avec for…of et la déstructuration
    for (const [cle, valeur] of Object.entries({ nom: 'Sami', groupe: 'DEV1' })) {
      console.log(`${cle} = ${valeur}`);
    }
    1. Chaînage optionnel ?. et coalescence des nuls ??

      • ?. (ES2020) lit une propriété seulement si l'objet existe ; sinon il renvoie undefined au lieu de provoquer une erreur.
      • ?? (ES2020) donne une valeur de remplacement seulement si la valeur est null ou undefined. Contrairement à ||, il garde les valeurs 0, '' et false.
    const reponse = { stagiaire: { nom: 'Amira', adresse: null } };
    
    // console.log(reponse.stagiaire.adresse.ville);   // TypeError : adresse est null
    console.log(reponse.stagiaire?.adresse?.ville);    // undefined, sans erreur
    console.log(reponse.formateur?.nom ?? 'non affecté');   // non affecté
    
    // || ou ?? : la différence
    console.log(0 || 20);    // 20 : 0 est considéré comme « faux »
    console.log(0 ?? 20);    // 0  : 0 n'est ni null ni undefined
    
    // ??= (ES2021) : affecter seulement si la valeur est absente
    const options = {};
    options.theme ??= 'clair';
    console.log(options);    // { theme: 'clair' }
    • Dans Angular : on écrit souvent {{ utilisateur?.nom ?? 'Invité' }} dans un template, pour afficher une donnée qui n'est pas encore chargée sans faire planter la page.
  • Map et Set

    • Set est une collection de valeurs uniques; Map associe des clés à des valeurs, avec des clés de n'importe quel type.
    const langages = new Set(['JS', 'TS', 'JS']);
    console.log(langages.size, [...langages]);          // 2 [ 'JS', 'TS' ] : doublon supprimé
    
    // Méthodes des ensembles (ES2025)
    const front = new Set(['HTML', 'CSS', 'JS']);
    const back = new Set(['PHP', 'JS']);
    console.log([...front.union(back)]);                // [ 'HTML', 'CSS', 'JS', 'PHP' ]
    console.log([...front.intersection(back)]);         // [ 'JS' ]
    
    const notes = new Map([['Amira', 15.5]]);
    notes.set('Sami', 9.75);
    console.log(notes.get('Sami'), notes.size);         // 9.75 2
    1. La programmation asynchrone : promesses et async / await

      • Une opération asynchrone prend du temps (appel à une API, lecture d'un fichier) : le programme continue sans l'attendre et traite le résultat plus tard.
      • Une promesse (Promise, ES6) représente ce résultat futur. Elle est soit tenue (resolve), soit rompue (reject).
      • async / await (ES2017) permet d'écrire ce code comme s'il était séquentiel ; les erreurs se gèrent avec try / catch.
    // async.mjs
    function chargerStagiaire(id) {
      return new Promise((resolve, reject) => {
        setTimeout(() => {                       // simule une API qui répond en 1 seconde
          if (id > 0) resolve({ id, nom: 'Amira', groupe: 'DEV1' });
          else reject(new Error(`Stagiaire ${id} introuvable`));
        }, 1000);
      });
    }
    
    // 1) Avec then / catch / finally
    console.log('1. Demande envoyée');
    chargerStagiaire(1)
      .then((s) => console.log('3. Reçu :', s.nom))
      .catch((e) => console.error('Erreur :', e.message))
      .finally(() => console.log('4. Terminé'));
    console.log('2. Suite du programme');
    
    // 2) Avec async / await
    async function afficher(id) {
      try {
        const s = await chargerStagiaire(id);
        console.log(`async/await : ${s.nom} (${s.groupe})`);
      } catch (e) {
        console.error('async/await, erreur :', e.message);
      }
    }
    await afficher(1);
    await afficher(-5);
    
    // 3) Plusieurs promesses en parallèle
    const [a, b] = await Promise.all([chargerStagiaire(1), chargerStagiaire(2)]);
    console.log('Promise.all :', a.id, b.id);
    node async.mjs
    1. Demande envoyée
    2. Suite du programme
    3. Reçu : Amira
    4. Terminé
    async/await : Amira (DEV1)
    async/await, erreur : Stagiaire -5 introuvable
    Promise.all : 1 2
    • await est utilisé ici directement au niveau du fichier : c'est possible dans un module (.mjs) depuis ES2022. Ailleurs, await doit être placé dans une fonction async.
    • Dans Angular : le client HTTP renvoie des Observables (bibliothèque RxJS) plutôt que des promesses. Le principe est le même : demander une donnée, continuer, puis traiter la réponse quand elle arrive. Ils seront étudiés dans le chapitre Communiquer via le client HTTP.
  • ECMAScript moderne dans le code Angular

    • Voici un composant Angular annoté : chaque numéro correspond à une notion de cette leçon.

    Les notions ECMAScript dans un composant Angular

    1. Compatibilité : du code moderne au navigateur

      • Dans un projet Angular, le compilateur transforme le TypeScript en JavaScript selon la version indiquée par l'option target du fichier tsconfig.json (ES2022 dans les projets récents).
      • Cette transformation convertit la syntaxe (les mots-clés et opérateurs). En revanche, les nouvelles fonctions de la bibliothèque standard (Object.groupBy(), Set.prototype.union()…) doivent exister dans le navigateur lui-même. Avant de les utiliser dans une application destinée au public, vérifiez leur prise en charge sur caniuse.com ou sur MDN.

    De TypeScript au JavaScript exécuté par le navigateur

    1. Activité 5 : moderniser un programme ES5

      • Le programme ci-dessous, écrit en ES5, calcule les bulletins d'un groupe de stagiaires. Le copier dans 05-es5.js, l'exécuter et noter le résultat.
    var stagiaires = [
      { nom: 'Amira', groupe: 'DEV1', notes: [14, 16, 17] },
      { nom: 'Sami', groupe: 'DEV2', notes: [8, 11, 9] },
      { nom: 'Yasmine', groupe: 'DEV1', notes: [12, 10, 15] },
      { nom: 'Karim', groupe: 'DEV2', notes: [18, 16, 19] }
    ];
    
    function calculerMoyenne(notes) {
      var somme = 0;
      for (var i = 0; i < notes.length; i++) {
        somme = somme + notes[i];
      }
      return somme / notes.length;
    }
    
    function Bulletin(stagiaire) {
      this.nom = stagiaire.nom;
      this.groupe = stagiaire.groupe;
      this.moyenne = calculerMoyenne(stagiaire.notes);
    }
    
    Bulletin.prototype.afficher = function () {
      var mention = this.moyenne >= 10 ? 'Admis' : 'Ajourné';
      return this.nom + ' (' + this.groupe + ') : ' + this.moyenne.toFixed(2) + ' / 20 - ' + mention;
    };
    
    var bulletins = [];
    for (var j = 0; j < stagiaires.length; j++) {
      bulletins.push(new Bulletin(stagiaires[j]));
    }
    
    var admis = [];
    for (var k = 0; k < bulletins.length; k++) {
      if (bulletins[k].moyenne >= 10) {
        admis.push(bulletins[k].nom);
      }
    }
    
    for (var m = 0; m < bulletins.length; m++) {
      console.log(bulletins[m].afficher());
    }
    console.log('Admis : ' + admis.join(', '));
    console.log('Nombre d\'admis : ' + admis.length + ' sur ' + bulletins.length);
    • Dans 05-moderne.js, réécrire ce programme en JavaScript moderne, sans aucune boucle for ni var:
      • const partout ;
      • calculerMoyenne en fonction fléchée avec reduce;
      • Bulletin en classe, avec un constructeur qui déstructure le stagiaire ;
      • afficher() avec un gabarit;
      • les bulletins avec map, les admis avec filter puis map, l'affichage avec forEach.
    • Vérifier que les deux programmes affichent exactement le même résultat :
    Amira (DEV1) : 15.67 / 20 - Admis
    Sami (DEV2) : 9.33 / 20 - Ajourné
    Yasmine (DEV1) : 12.33 / 20 - Admis
    Karim (DEV2) : 17.67 / 20 - Admis
    Admis : Amira, Yasmine, Karim
    Nombre d'admis : 3 sur 4

    Comparaison d'un programme en ES5 et en ES6+

    • Compter les lignes des deux versions et noter la différence en commentaire. Commit : git commit -am "Activité 5 : programme modernisé" (pensez d'abord à git add pour les nouveaux fichiers).
  • Activité 6 : découper en modules

    • Créer un dossier 06-modules/ et répartir le programme moderne de l'activité 5 dans trois fichiers :
      • donnees.mjs: export nommé du tableau stagiaires;
      • bulletin.mjs: export nommé de calculerMoyenne et export par défaut de la classe Bulletin;
      • index.mjs: importe le reste et affiche le résultat.
    • Exécuter avec node 06-modules/index.mjs: le résultat doit être identique à celui de l'activité 5.
    • Répondre en commentaire dans index.mjs: que se passe-t-il si on oublie le mot-clé export devant calculerMoyenne? Quel message affiche Node ?
  • Activité 7 : simuler un appel à une API

    • Dans 07-async.mjs, écrire une fonction chargerGroupe(code) qui renvoie une promesse tenue après 800 ms avec la liste des stagiaires du groupe ('DEV1' ou 'DEV2'), ou rompue avec l'erreur Groupe XXX inconnu pour tout autre code.
    • Écrire une fonction async afficherGroupe(code) qui affiche « Chargement de DEV1… », attend la liste avec await, puis affiche le nombre de stagiaires et leurs noms, ou le message d'erreur grâce à try / catch.
    • Appeler afficherGroupe('DEV1'), puis afficherGroupe('DEV9').
    • Charger enfin les deux groupes en parallèle avec Promise.all, mesurer la durée avec console.time('chargement') et console.timeEnd('chargement'), puis comparer avec deux await successifs. Expliquer la différence de durée en commentaire.
  • Activité 8 : défi ES2020 à ES2025

    • Dans 08-defi.js, à partir des bulletins de l'activité 5, produire l'affichage ci-dessous en utilisant obligatoirement: toSorted(), at(-1), Object.groupBy(), Object.entries() avec for…of, ?. et ??.
    Classement : 1. Karim | 2. Amira | 3. Yasmine | 4. Sami
    Major : Karim – Dernier : Sami
    DEV1 : 2 stagiaires, moyenne 14.00
    DEV2 : 2 stagiaires, moyenne 13.50
    Sami : 9.33 – Ali : introuvable
    • Vérifier en fin de programme que le tableau d'origine n'a pas été trié : son premier élément doit toujours être Amira. Pourquoi ce ne serait pas le cas avec sort()?
    • Créer un fichier README.md qui liste les activités et les notions utilisées dans chacune, puis faire le dernier commit et le push.
  • Erreurs fréquentes

  • Message ou symptôme Cause Solution
    TypeError: Assignment to constant variable. Réaffectation d'une variable déclarée avec const Utiliser let si la valeur doit vraiment changer
    ReferenceError: Cannot access 'x' before initialization Variable let ou const utilisée avant sa déclaration Déplacer la déclaration avant son utilisation
    Le gabarit affiche ${nom} tel quel Apostrophes ' ' au lieu d'accents graves ` ` Écrire la chaîne entre accents graves
    Une fonction fléchée renvoie undefined Accolades sans return, ou objet renvoyé sans parenthèses Ajouter return, ou écrire () => ({ … })
    SyntaxError: Cannot use import statement outside a module import dans un script classique : balise <script> sans type="module" dans le navigateur, ou fichier .js avec une ancienne version de Node.js Ajouter type="module" à la balise <script>; avec Node, renommer en .mjs ou ajouter "type": "module" dans package.json
    SyntaxError: The requested module does not provide an export named 'x' Oubli de export, faute de frappe, ou export par défaut importé avec des accolades Vérifier le nom et le type d'export
    TypeError: Cannot read properties of undefined Accès à la propriété d'un objet absent Utiliser ?. et prévoir une valeur avec ??
    Le résultat d'une promesse s'affiche sous la forme Promise { <pending> } Oubli de await (ou de .then()) Ajouter await dans une fonction async
    TypeError: x.toSorted is not a function (ou groupBy, union…) Version de Node.js ou de navigateur trop ancienne Mettre à jour Node.js (version LTS) ou vérifier la compatibilité du navigateur
    1. À retenir

      • ECMAScript est la norme, JavaScript son implémentation ; TypeScript ajoute les types et les décorateurs à ECMAScript.
      • ES6 (2015) a modernisé le langage ; depuis, une version sort chaque année.
      • const par défaut, let si la valeur change, jamais var.
      • Fonctions fléchées, gabarits, déstructuration, spread / rest et méthodes de tableaux rendent le code plus court et plus lisible.
      • Les classes et les modules (import / export) sont la base de toute l'architecture d'Angular.
      • ?. et ?? protègent contre les données absentes ; async / await simplifie le code asynchrone.
    Lire du code Angular, c'est d'abord lire de l'ECMAScript moderne : chaque notion de cette leçon réapparaîtra dans vos composants et vos services.