162 lines
6.8 KiB
Markdown
162 lines
6.8 KiB
Markdown
# 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.
|
|
|
|
```mermaid
|
|
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).
|
|
|
|
```mermaid
|
|
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.
|
|
|
|
```mermaid
|
|
graph TD
|
|
A[chaufferEau] --> B[preparerSauce];
|
|
B --> C[cuirePates];
|
|
C --> D[melangerPatesEtSauce];
|
|
D --> E[Plat prêt];
|
|
```
|
|
|
|
2. 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.
|
|
|
|
```mermaid
|
|
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.
|