MaĂźtriser async/await

try/catch, Promise.all() & Injection de dépendances

Utilisez les flĂšches, cliquez ou glissez pour naviguer

Objectifs de la leçon

1. MaĂźtriser async/await

Syntaxe lisible pour les Promises

2. Gérer les erreurs avec try/catch

Comme en code synchrone

3. Utiliser Promise.all()

Opérations parallÚles

4. Séparation service / storage

Injection de dépendances manuelle

5. Construire un gestionnaire de tĂąches CRUD

Projet pratique avec architecture propre

Plan du cours

1

Récap J4 : Promises, .then()/.catch()

Ce qu'on sait dĂ©jĂ  — aujourd'hui on va encore plus loin

2

async/await

MĂȘme rĂ©sultat que .then(), mais ressemble Ă  du code synchrone

3

try/catch avec await

Gestion d'erreurs naturelle, comme en synchrone

4

Promise.all()

ExĂ©cuter en parallĂšle — 4 requĂȘtes de 500ms = ~500ms au total

5

Injection de dépendances

Service reçoit storage en paramÚtre = contrat, pas implémentation

Récap J4

Promises, .then()/.catch(), chainage

Ce qu'on sait déjà :

new Promise() · .then() · .catch()

Ce qu'on sait déjà : .then()/.catch()

// J4 : chaĂźner les Promises

fetch('/api/users')

.then((res) => res.json())

.then((users) => {

console.log(users);

})

.catch((err) => {

console.error(err);

});

💡 Ça marche... mais ça peut devenir difficile Ă  lire quand on enchaĂźne beaucoup d'opĂ©rations

Le problĂšme avec .then()

// 3 opérations enchaßnées

getUser()

.then((user) => getProfile(user.id))

.then((profile) => getSettings(profile.id))

.then((settings) => {

// Mais si j'ai besoin de user ET profile ici?

// Ils ne sont plus accessibles! đŸ˜±

})

.catch((err) => console.error(err));

❌ Chaque .then() n'a accĂšs qu'Ă  la valeur prĂ©cĂ©dente — les variables se perdent

✅ Solution : async/await — toutes les variables restent accessibles!

async / await

MĂȘme rĂ©sultat, syntaxe synchrone

async = la fonction retourne une Promise

await = attendre la résolution

Avant .then() vs AprĂšs async/await

❌ Avec .then()

fetch('/api/user')

.then(res => res.json())

.then(user => {

console.log(user);

});

Callback imbriqué

✅ Avec async/await

async function getUser() {

const res = await fetch('/api/user');

const user = await res.json();

console.log(user);

}

Linéaire, lisible!

💡 C'est exactement la mĂȘme chose sous le capot — async/await est du "sucre syntaxique" pour les Promises

Le mot-clé async

Ajouter async devant une fonction la fait toujours retourner une Promise

async function saluer() {

return 'Bonjour!';

}

// MĂȘme si on retourne une string...

// La fonction renvoie une Promise!

saluer().then(msg => console.log(msg));

// → "Bonjour!"

🎯 async = "cette fonction retourne une Promise" — automatiquement!

Le mot-clé await

await "met en pause" la fonction jusqu'à ce que la Promise soit résolue

async function chargerUtilisateur() {

// ⏞ On attend la rĂ©ponse

const res = await fetch('/api/user');

// ⏞ On attend le parsing JSON

const user = await res.json();

// ✅ On a les donnĂ©es!

console.log(user.name);

}

💡 Le code a l'air synchrone, mais il est toujours asynchrone! Le thread n'est pas bloquĂ©.

await : pas Ă  pas

1ïžâƒŁ

fetch() lance la requĂȘte et retourne une Promise

La Promise est en état Pending

2ïžâƒŁ

await "suspend" la fonction

Le reste du code hors de cette fonction continue de s'exécuter

3ïžâƒŁ

Quand la Promise est résolue

await récupÚre la valeur et la fonction reprend

4ïžâƒŁ

res contient maintenant la vraie valeur

On peut l'utiliser comme n'importe quelle variable

async avec les arrow functions

// Arrow function classique

const getUser = async () => {

const res = await fetch('/api/user');

return res.json();

};

❌ () async =>

async APRÈS les parenthÚses

✅ async () =>

async AVANT les parenthĂšses

💡 Dans une mĂ©thode de classe : async maMethode() { await ... }

try / catch

Gérer les erreurs comme en synchrone

Avec .then() → .catch()

Avec await → try/catch

.catch() vs try/catch

❌ Avec .catch()

fetch('/api/user')

.then(res => res.json())

.then(user => {

// utiliser user

})

.catch(err => {

console.error(err);

});

Erreur Ă  la fin, loin du code

✅ Avec try/catch

try {

const res = await fetch('/api/user');

const user = await res.json();

// utiliser user

} catch (err) {

console.error(err);

}

Erreur juste à cÎté du code!

Syntaxe de try/catch avec await

async function chargerDonnees() {

try {

// Code qui peut échouer

const data = await fetchData();

console.log(data);

} catch (error) {

// Si une Promise est rejetĂ©e → on arrive ici

console.error('Erreur:', error.message);

}

}

try

Le code Ă  essayer

catch

Si erreur → ici

finally

Toujours exécuté

🎯 Si un await rejette, l'exĂ©cution saute directement au catch — comme throw en synchrone

finally : toujours exécuté

async function chargerDonnees() {

try {

const data = await fetchData();

return data;

} catch (error) {

console.error(error);

} finally {

// Exécuté dans TOUS les cas

hideLoadingSpinner();

}

}

💡 finally = parfait pour le nettoyage (loading spinner, fermeture de connexion, etc.)

⚠ PiĂšge : oublier async

await NE PEUT ĂȘtre utilisĂ© que dans une fonction async!

❌ Erreur

function getUser() {

// Pas de async!

const res = await fetch('/api');

// → SyntaxError!

}

✅ Correct

async function getUser() {

// async est lĂ !

const res = await fetch('/api');

// → OK!

}

💡 Erreur trĂšs courante! Si vous avez un await, vĂ©rifiez TOUJOURS que la fonction a async

⚠ PiĂšge : await au niveau racine

On ne peut PAS utiliser await directement au niveau racine d'un fichier

❌ Top-level await

// fichier.js

const data = await fetch('/api');

// → SyntaxError!

✅ IIFE ou fonction async

(async () => {

const data = await fetch('/api');

})();

💡 IIFE = Immediately Invoked Function Expression — on crĂ©e et appelle une fonction async tout de suite

Promise.all()

Exécuter en parallÚle

4 requĂȘtes de 500ms en sĂ©quentiel = 2000ms

4 requĂȘtes de 500ms avec Promise.all = ~500ms

Séquentiel vs ParallÚle

❌ SĂ©quentiel

const a = await fetch('/api/a');

const b = await fetch('/api/b');

const c = await fetch('/api/c');

const d = await fetch('/api/d');

RequĂȘte A ⏳ 500ms
RequĂȘte B ⏳ 500ms
RequĂȘte C ⏳ 500ms
RequĂȘte D ⏳ 500ms

Total : ~2000ms

✅ Parallùle

const [a, b, c, d] = await Promise.all([

fetch('/api/a'),

fetch('/api/b'),

fetch('/api/c'),

fetch('/api/d'),

]);

A, B, C, D en mĂȘme temps!

Total : ~500ms

Syntaxe de Promise.all()

const [users, posts, comments] = await Promise.all([

fetchUsers(),

fetchPosts(),

fetchComments(),

]);

1. On passe un tableau de Promises

Toutes sont lancĂ©es en mĂȘme temps

2. On await le résultat de Promise.all()

On attend que TOUTES soient résolues

3. On reçoit un tableau avec les résultats

L'ordre correspond à l'ordre du tableau d'entrée

Destructuring du résultat

// Sans destructuring

const results = await Promise.all([fetchA(), fetchB()]);

const a = results[0];

const b = results[1];

// Avec destructuring ✅

const [a, b] = await Promise.all([fetchA(), fetchB()]);

💡 L'ordre est garanti : le 1er rĂ©sultat correspond Ă  la 1Ăšre Promise, mĂȘme si la 2Ăšme finit avant

⚠ PiĂšge : Promise.all()

Si UNE SEULE Promise échoue, TOUT échoue

3 réussites + 1 échec = rejet complet

On perd les 3 résultats qui ont réussi!

Solution : Promise.allSettled()

const results = await Promise.allSettled([

fetchA(), // ✅ rĂ©ussit

fetchB(), // ❌ Ă©choue

fetchC(), // ✅ rĂ©ussit

]);

// results = [

// { status: 'fulfilled', value: 'data A' },

// { status: 'rejected', reason: Error },

// { status: 'fulfilled', value: 'data C' }

// ]

Promise.all()

Tout ou rien — une erreur = tout Ă©choue

Promise.allSettled()

On récupÚre les résultats de chacune

Injection de dépendances

Le service dépend du CONTRAT, pas de l'implémentation

Sans framework — juste un paramùtre!

Le problĂšme : couplage fort

// ❌ Le service CRÉE son propre storage

class TaskService {

constructor() {

// Couplé à UNE implémentation!

this.storage = new JsonFileStorage('tasks.json');

}

}

❌ Impossible de tester sans fichier JSON

Le service est figé sur JsonFileStorage

❌ Impossible de changer pour MySQL

Il faut modifier le code de TaskService

❌ Impossible d'utiliser un storage en mĂ©moire

Pour les tests unitaires par exemple

La solution : injecter le storage

// ✅ Le service REÇOIT le storage en paramùtre

class TaskService {

constructor(storage) {

// Le storage est injecté!

this.storage = storage;

}

}

✅ Testable — injecter un MockStorage

✅ Flexible — changer de storage sans toucher au service

🎯 C'est ça l'injection de dĂ©pendances — un paramĂštre constructeur!

Pas besoin de framework. C'est "trop simple"? C'est exactement ça.

Contrat vs Implémentation

Le service ne dĂ©pend que du CONTRAT (les mĂ©thodes attendues), pas de l'IMPLÉMENTATION

📋 Contrat (interface)

Ce que le storage DOIT avoir :

// Méthodes requises :

getAll()

getById(id)

create(item)

update(id, item)

delete(id)

🔧 ImplĂ©mentations

Différentes façons de stocker :

JsonFileStorage
MemoryStorage
SqliteStorage
MockStorage

💡 Tant que l'implĂ©mentation respecte le contrat, le service fonctionne — c'est le principe!

Architecture du gestionnaire de tĂąches

📁

storage/

Interface + Implémentations (JSON, Memory)

📁

services/

Logique mĂ©tier — CRUD, validation

📁

index.js

Point d'entrĂ©e — injection des dĂ©pendances

🎯 SĂ©paration claire : storage s'occupe de la persistance, service de la logique

L'interface Storage (le contrat)

// storage/interface.js

class TaskStorage {

async getAll() { throw new Error('Not implemented'); }

async getById(id) { throw new Error('Not implemented'); }

async create(task) { throw new Error('Not implemented'); }

async update(id, task) { throw new Error('Not implemented'); }

async delete(id) { throw new Error('Not implemented'); }

}

💡 En JS pas d'interface native — on utilise une classe avec des mĂ©thodes qui throw, ou juste JSDoc

Implémentation : JsonFileStorage

// storage/json-file-storage.js

const fs = require('fs/promises');

class JsonFileStorage {

constructor(filePath) {

this.filePath = filePath;

}

async getAll() {

const data = await fs.readFile(this.filePath, 'utf8');

return JSON.parse(data);

}

async create(task) {

const tasks = await this.getAll();

tasks.push(task);

await fs.writeFile(this.filePath, JSON.stringify(tasks));

return task;

}

}

💡 fs/promises = version Promise du module fs — parfait avec await!

Implémentation : MemoryStorage

// storage/memory-storage.js

class MemoryStorage {

constructor() {

this.tasks = [];

}

async getAll() {

return [...this.tasks];

}

async create(task) {

this.tasks.push(task);

return task;

}

}

💡 MĂȘme contrat, mais en mĂ©moire — parfait pour les tests! Pas de fichier, pas de I/O

TaskService : la logique métier

// services/task-service.js

class TaskService {

// Le storage est INJECTÉ

constructor(storage) {

this.storage = storage;

}

async getAllTasks() {

return this.storage.getAll();

}

async createTask(title, description) {

// Validation = logique métier

if (!title) throw new Error('Title is required');

const task = { id: Date.now(), title, description, done: false };

return this.storage.create(task);

}

}

🎯 Le service ne sait PAS comment les donnĂ©es sont stockĂ©es — il dĂ©lĂšgue au storage

TaskService : CRUD complet avec try/catch

class TaskService {

constructor(storage) { this.storage = storage; }

async deleteTask(id) {

try {

const task = await this.storage.getById(id);

if (!task) throw new Error('Task not found');

await this.storage.delete(id);

return { success: true };

} catch (error) {

throw new Error(`Delete failed: ${error.message}`);

}

}

}

💡 try/catch + await = gestion d'erreurs propre et lisible — le pattern standard

index.js : l'injection en action

// index.js — point d'entrĂ©e

const { JsonFileStorage } = require('./storage/json-file-storage');

const { TaskService } = require('./services/task-service');

// 🔑 C'est ICI qu'on choisit l'implĂ©mentation!

const storage = new JsonFileStorage('tasks.json');

const taskService = new TaskService(storage);

// Pour les tests, on changerait juste :

// const storage = new MemoryStorage();

// const taskService = new TaskService(storage);

🎯 Une seule ligne Ă  changer pour passer de JSON Ă  Memory — le service n'est jamais modifiĂ©!

PiĂšges courants

❌ Oublier le mot-clĂ© async

await ne fonctionne QUE dans une fonction async — erreur la plus frĂ©quente!

❌ Promise.all() Ă©choue entiĂšrement si une seule Promise Ă©choue

Utiliser Promise.allSettled() si on veut récupérer les résultats partiels

💡 L'injection de dĂ©pendances sans framework peut sembler "trop simple"

C'est exactement ça — un paramùtre constructeur. Pas besoin de magie!

À retenir!

async function retourne toujours une Promise

await "met en pause" jusqu'à la résolution

try/catch avec await = le pattern standard

Gestion d'erreurs naturelle, comme en synchrone

Promise.all() pour le parallelisme

Toutes les Promises en mĂȘme temps — gain de temps Ă©norme

Le service dĂ©pend du CONTRAT, pas de l'IMPLÉMENTATION

C'est l'injection de dĂ©pendances — un paramĂštre, pas un framework

Exercices pratiques

1. Convertir .then() en async/await

Reprendre un exercice J4 et le réécrire avec await

2. Ajouter try/catch pour gérer les erreurs

Wrapper chaque opération async dans un try/catch

3. Créer MemoryStorage + JsonFileStorage

Implémenter le contrat TaskStorage pour les deux

4. Construire le CRUD complet avec TaskService

Create, Read, Update, Delete avec injection de dépendances

5. Utiliser Promise.all() pour charger plusieurs fichiers

Charger users.json, tasks.json, settings.json en parallĂšle

Questions?

async/await + DI = du code propre et testable

Pratiquez avec les exercices!

async/await, try/catch, Promise.all(), injection de dépendances