2025-12-16 09:52:33 +01:00
2025-12-15 16:45:48 +01:00
2025-12-16 09:52:33 +01:00
2025-12-16 09:52:33 +01:00
2025-12-15 16:45:48 +01:00

TP : préparation d'un café (JavaScript asynchrone)

Informations générales

Cours : JavaScript Avancé > Programmation concurrente > Tâches IO-bound concurrentes avec async/await
Objectifs pédagogiques :

  • JavaScript avancé : programmation asynchrone
  • Prise en main de WebStorm
  • Bonnes pratiques de l'entreprise

Prérequis

Connaissances préalables

  • Connaissances de base en programmation

Énoncé

L'objectif de ce TP est de comprendre et de maîtriser les concepts de la programmation asynchrone en JavaScript. Nous allons simuler la préparation d'un café en passant d'une approche "classique" basée sur les callbacks à une implémentation moderne et optimisée avec les Promises et la syntaxe async/await.

Contexte

La préparation d'un café est une séquence d'opérations qui prennent du temps (chauffer l'eau, moudre les grains, etc.). En JavaScript, de telles opérations (comme les requêtes réseau ou les accès aux fichiers) sont non-bloquantes. Nous allons simuler ce comportement avec setTimeout.

Partie 1 : la machine à café "Callback"

Dans cette première partie, nous allons construire la logique de notre machine à café en utilisant des fonctions asynchrones qui prennent une fonction de rappel (callback) comme dernier argument.

Objectifs

  1. Créer des fonctions asynchrones de base.
  2. Comprendre l'enchaînement d'opérations asynchrones.
  3. Mettre en évidence le problème du "Callback Hell" (la pyramide de l'enfer).

Instructions

const TEMPS_SELECTION = 500;
const TEMPS_MOUTURE = 700;
const TEMPS_CHAUFFAGE = 1000;
const TEMPS_PREPARATION = 500;

function selectionnerCafe(nomCafe, callback) {
    console.log(`1. Sélection du café : ${nomCafe}...`);
    setTimeout(() => {
        console.log(`   -> Café "${nomCafe}" sélectionné.`);
        // Le premier argument est pour une erreur potentielle (ici, null).
        // Le second est le résultat.
        callback(null, nomCafe);
    }, TEMPS_SELECTION);
}
  1. Configuration Créez des constantes pour définir les temps de simulation de chaque étape :

    • TEMPS_SELECTION: 500 ms
    • TEMPS_MOUTURE: 700 ms
    • TEMPS_CHAUFFAGE: 1000 ms
    • TEMPS_PREPARATION: 500 ms
  2. Fonctions de base Implémentez les quatre fonctions suivantes. Chaque fonction doit simuler une tâche asynchrone avec setTimeout (cf exemple ci-dessus) et utiliser des console.log pour afficher le début et la fin de chaque étape.

    Le dernier argument de chaque fonction doit être un callback. Ce callback respectera la convention Node.js : callback(erreur, resultat). S'il n'y a pas d'erreur, le premier argument sera null.

    • selectionnerCafe(nomCafe, callback)

      • Affiche 1. Sélection du café : [nomCafe]...
      • Après TEMPS_SELECTION, affiche -> Café "[nomCafe]" sélectionné.
      • Appelle le callback avec (null, nomCafe).
    • moudreGrains(nomCafe, callback)

      • Affiche 2. Broyage des grains pour [nomCafe]...
      • Après TEMPS_MOUTURE, affiche -> Grains moulus.
      • Appelle le callback avec (null, 'grains-moulus').
    • chaufferEau(temperature, callback)

      • Affiche 3. Chauffage de l'eau à [temperature]°C...
      • Après TEMPS_CHAUFFAGE, affiche -> Eau chaude prête.
      • Appelle le callback avec (null, 'eau-chaude').
    • preparerCafe(typeCafe, grains, eau, callback)

      • Affiche 4. Préparation du café [typeCafe]...
      • Après TEMPS_PREPARATION, affiche -> Café [typeCafe] préparé..
      • Appelle le callback avec (null, 'café [typeCafe] préparé').
  3. L'Enchaînement infernal Maintenant, utilisez les fonctions que vous venez de créer pour préparer un "Expresso" chauffé à 90°C.

    • Pour garantir que les étapes se déroulent dans le bon ordre, vous devrez imbriquer les appels : le callback de selectionnerCafe doit appeler moudreGrains, son callback doit appeler chaufferEau, et ainsi de suite.
    • À chaque étape, vérifiez la présence d'une erreur potentielle avant de continuer.
    • Lorsque la préparation est terminée, affichez un message de succès : SUCCÈS : Votre [café final] est servi !
    • Ajoutez un console.log tout à la fin de votre script pour montrer que le code principal continue de s'exécuter pendant la préparation du café.
graph TD
    subgraph "Pyramide des callbacks"
        A[selectionnerCafe] --> B{Callback 1}
        B --> C[moudreGrains]
        C --> D{Callback 2}
        D --> E[chaufferEau]
        E --> F{Callback 3}
        F --> G[preparerCafe]
        G --> H{Callback 4}
        H --> I[Succès]
    end

Question : Observez la structure de votre code (et le diagramme ci-dessus). Pourquoi cette imbrication est-elle appelée le "Callback Hell" ? Quels problèmes cela pose-t-il en termes de lisibilité et de maintenance ?


Partie 2 : modernisation avec async/await

Nous allons maintenant refactoriser notre code pour utiliser les Promises et la syntaxe moderne async/await. Cela nous permettra d'écrire un code asynchrone qui ressemble à du code synchrone, tout en optimisant les performances.

Objectifs

  1. Transformer des fonctions à base de callbacks en fonctions retournant des Promises.
  2. Utiliser async/await pour orchestrer des tâches asynchrones de manière lisible.
  3. Gérer les erreurs proprement avec des blocs try...catch.
  4. Optimiser les performances en exécutant des tâches indépendantes en parallèle avec Promise.all.

Instructions

  1. "Promisification" de l'API Regroupez la logique de la machine à café dans un objet coffeeApi. Transformez chaque fonction de la partie 1 pour qu'elle retourne une Promise au lieu d'utiliser un callback.

    • Ajoutez une nouvelle fonction : nettoyerTasse() qui prend 600ms et résout la promesse avec la valeur 'tasse-propre'.
    • Dans selectionnerCafe, ajoutez une gestion d'erreur : si coffeeName est nul ou vide, la Promise doit être rejetée avec une Error("Aucun type de café n'a été spécifié.").
    // exemple de transformation pour selectionnerCafe
    const coffeeApi = {
        selectionnerCafe(coffeeName) {
            return new Promise((resolve, reject) => {
                console.log(`  - Sélection du café : ${coffeeName}...`);
                setTimeout(() => {
                    if (!coffeeName) {
                        return reject(new Error("Aucun type de café n'a été spécifié."));
                    }
                    console.log(`    -> Café "${coffeeName}" sélectionné.`);
                    resolve(coffeeName);
                }, config.TEMPS_SELECTION);
            });
        },
        // autres fonctions ici
    };
    
  2. Scénario 1 : préparation séquentielle
    Créez une fonction async function runSequentialPreparation().

    • À l'intérieur, utilisez await pour préparer un café "Lungo" en suivant strictement cet ordre :
      1. Sélectionner le café.
      2. Moudre les grains.
      3. Chauffer l'eau (à 95°C).
      4. Nettoyer la tasse.
      5. Préparer le café final.
    • Encadrez votre logique dans un bloc try...catch pour afficher un message d'erreur clair en cas d'échec.
    • Utilisez console.time("Temps total") et console.timeEnd("Temps total") pour mesurer la durée totale de ce processus.
graph TD
    A[Début] --> B["await selectionnerCafe Lungo"]
    B --> C["await moudreGrains"]
    C --> D["await chaufferEau 95°C"]
    D --> E["await nettoyerTasse"]
    E --> F["await preparerCafe"]
    F --> G[Fin]
  1. Scénario 2 : préparation optimisée
    Certaines tâches peuvent être effectuées en même temps ! Le broyage des grains, le chauffage de l'eau et le nettoyage de la tasse sont des opérations indépendantes.

    • Créez une nouvelle fonction async function runOptimizedPreparation().
    • Commencez par await la sélection d'un café "Americano".
    • Ensuite, lancez le broyage des grains, le chauffage de l'eau (à 92°C) et le nettoyage de la tasse de manière concurrente en utilisant Promise.all().
    • Une fois que toutes ces tâches sont terminées, await la préparation finale du café.
    • Utilisez également try...catch et console.time() / console.timeEnd() pour la gestion des erreurs et la mesure du temps.
graph TD
    A[Début] --> B["await selectionnerCafe Americano"]
    B --> C{Lancement parallèle Promise.all}
    C --> D[moudreGrains]
    C --> E["chaufferEau 92°C"]
    C --> F[nettoyerTasse]
    D --> G{Synchronisation}
    E --> G
    F --> G
    G --> H[await preparerCafe]
    H --> I[Fin]
  1. Point d'entrée
    Créez une fonction async function main() qui appellera runSequentialPreparation() puis runOptimizedPreparation() pour lancer les deux scénarios l'un après l'autre.

Question : Comparez les temps d'exécution des scénarios 1 et 2. Expliquez la différence de performance observée. Quel est l'avantage principal de Promise.all ici ?

Description
No description provided
Readme 36 KiB
Languages
JavaScript 100%