Files
ENI-JSAdvanced_02/README.md
2025-12-15 16:08:10 +01:00

6.8 KiB

TD - Démonstration JavaScript Asynchrone : la cuisine

Dans cette démonstration, l'objectif est de comprendre les concepts fondamentaux de la programmation asynchrone en JavaScript.

Nous allons simuler la préparation d'un plat de pâtes pour illustrer :

  1. La modularité du code avec une "API".
  2. L'utilisation des Promises pour gérer des opérations qui prennent du temps.
  3. La syntaxe async/await pour écrire un code asynchrone clair et lisible.
  4. L'optimisation des tâches parallèles avec Promise.all.
  5. La gestion des erreurs avec try/catch.

Prérequis

  • Avoir Node.js installé sur votre machine.

Étape 1 : Callback hell (l'enfer des callbacks)

Observez le code fourni dans le fichier callback_hell.js. Ce code simule l'interrogation d'une API pour récupérer des données utilisateur, les droits de cet utilisateur, ainsi que ses messages de blog. Chaque étape dépend de la précédente, ce qui crée une pyramide de callbacks difficile à lire et à maintenir.

graph TD
    subgraph "Pyramide de Callbacks"
        A[getUser] --> B{"Callback 1"};
        B --> C[getPermissions];
        C --> D{"Callback 2"};
        D --> E[getPosts];
        E --> F{"Callback 3"};
        F --> G[...];
    end

Dans la suite, nous utiliserons une approche plus moderne et plus propre pour gérer ce type de scénario.


Étape 2 : Création de notre API de Cuisine

Notre premier fichier, api_cuisson.js, contiendra toutes les fonctions de base pour préparer nos pâtes. Chaque fonction simulera une action (comme chauffer de l'eau) et retournera une Promise. Une Promise est un objet qui représente la complétion (ou l'échec) future d'une opération asynchrone.

Analyse de l'existant :

  • config : un objet simple pour centraliser les durées de chaque étape.
  • new Promise((resolve, reject) => { ... }) : c'est le cœur d'une fonction asynchrone.
    • le code à l'intérieur (ici, un setTimeout) s'exécute.
    • quand l'opération réussit, on appelle resolve() pour signaler que la promesse est tenue.
    • en cas d'erreur (comme dans preparerSauce), on appelle reject() pour signaler que la promesse est rompue.
  • export : ce mot-clé permet à d'autres fichiers (comme index.js) d'importer et d'utiliser ces fonctions.

Une promesse peut avoir trois états : en attente (pending), tenue (fulfilled) ou rompue (rejected).

graph TD
    A["pending 'état initial'"] --> B["fulfilled 'réussie'"];
    A --> C["rejected 'échouée'"];
    B --> D["Données retournées via .then"];
    C --> E["Erreur gérée via .catch"];

Le but est de compléter les TODO dans ce fichier, sur le modèle de la fonction chaufferEau.


Étape 3 : Orchestration avec async/await

Maintenant que notre "API" est prête, nous allons l'utiliser dans index.js pour créer nos scénarios de préparation. Nous utiliserons la syntaxe async/await qui est une manière moderne et très lisible de travailler avec les Promises.

Analyse de l'existant :

  • async function : déclare une fonction asynchrone. Seules les fonctions async peuvent utiliser le mot-clé await.
  • await : met en pause l'exécution de la fonction async et attend que la Promise soit résolue. Le code devient aussi simple à lire que du code synchrone !
  • try/catch : c'est le mécanisme standard pour gérer les erreurs. Si une Promise dans le bloc try est "rejetée" (reject), l'exécution saute directement au bloc catch.
  • Promise.all([...]) : c'est l'outil d'optimisation ! Il prend un tableau de Promises et attend qu'elles soient toutes terminées avec succès. C'est parfait pour les tâches qui ne dépendent pas les unes des autres (on peut faire la sauce PENDANT que l'eau chauffe).

Le but est de compléter les TODO dans ce fichier pour implémenter les deux scénarios :

  1. Préparation séquentielle (non optimisée) : chaque étape attend la fin de la précédente. On souhaite d'abord chauffer l'eau, puis préparer la sauce, ensuite cuire les pâtes, et enfin mélanger le tout.
graph TD
    A[chaufferEau] --> B[preparerSauce];
    B --> C[cuirePates];
    C --> D[melangerPatesEtSauce];
    D --> E[Plat prêt];
  1. Préparation optimisée (avec Promise.all) : chauffer l'eau et préparer la sauce en parallèle. On souhaite lancer ces deux tâches en même temps, puis attendre qu'elles soient toutes les deux terminées avec succès avant de cuire les pâtes et de mélanger le tout.
graph TD
    subgraph "Parallèle: Promise.all"
        A[chaufferEau]
        B[preparerSauce]
    end
    
    subgraph "Synchronisation"
      J["Attente de la fin des deux tâches"]
    end

    A --> J;
    B --> J;
    
    J --> C[cuirePates];
    C --> D[melangerPatesEtSauce];
    D --> E[Plat prêt];

Étape 4 : Exécution et analyse des résultats

Tout est en place ! Il est temps de lancer notre script et de voir la différence entre une préparation séquentielle et une préparation optimisée.

Placez-vous sur le fichier index.js et exécutez le fichier via le bouton "Run" de votre IDE.

Vous devriez avoir un résultat console similaire à celui-ci :

============================================================
Démonstration de code JavaScript asynchrone : la cuisine
============================================================

--- SCÉNARIO 1 : préparation séquentielle (non optimisée) ---
Lancement de la préparation pour : Spaghetti Bolognaise.
  - L'eau chauffe...
    -> L'eau est à ébullition.
  - Préparation de la sauce Bolognaise...
    -> Sauce Bolognaise prête.
  - Cuisson des Spaghetti...
    -> Spaghetti cuites.
  - Mélange des pâtes et de la sauce...
    -> Plat de Spaghetti sauce Bolognaise servi. Bon appétit !

SUCCÈS : votre 'Plat de Spaghetti sauce Bolognaise' est prêt !
Temps total (séquentiel): 4.152s

------------------------------------------------------------


--- SCÉNARIO 2 : préparation optimisée (avec Promise.all) ---
Lancement de la préparation pour : Penne Arrabbiata.
-> Lancement en parallèle du chauffage de l'eau et de la préparation de la sauce...
  - L'eau chauffe...
  - Préparation de la sauce Arrabbiata...
    -> Sauce Arrabbiata prête.
    -> L'eau est à ébullition.
-> Eau bouillante et sauce prête ! On peut lancer la cuisson des pâtes.
  - Cuisson des Penne...
    -> Penne cuites.
  - Mélange des pâtes et de la sauce...
    -> Plat de Penne sauce Arrabbiata servi. Bon appétit !

SUCCÈS (optimisé) : votre 'Plat de Penne sauce Arrabbiata' est prêt !
Temps total (optimisé): 2.921s

Conclusion

Remarquez la différence de temps total entre les deux scénarios. Dans le scénario 2, le temps total s'approche de celui de la tâche parallèle la plus longue (chaufferEau) plus le reste des étapes, ce qui est bien plus rapide que d'additionner toutes les durées.