try/catch, Promise.all() & Injection de dépendances
Utilisez les flĂšches, cliquez ou glissez pour naviguer
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
Récap J4 : Promises, .then()/.catch()
Ce qu'on sait dĂ©jĂ â aujourd'hui on va encore plus loin
async/await
MĂȘme rĂ©sultat que .then(), mais ressemble Ă du code synchrone
try/catch avec await
Gestion d'erreurs naturelle, comme en synchrone
Promise.all()
ExĂ©cuter en parallĂšle â 4 requĂȘtes de 500ms = ~500ms au total
Injection de dépendances
Service reçoit storage en paramÚtre = contrat, pas implémentation
Promises, .then()/.catch(), chainage
Ce qu'on sait déjà :
new Promise() · .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
// 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!
MĂȘme rĂ©sultat, syntaxe synchrone
async = la fonction retourne une Promise
await = attendre la résolution
â 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
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!
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Ă©.
fetch() lance la requĂȘte et retourne une Promise
La Promise est en état Pending
await "suspend" la fonction
Le reste du code hors de cette fonction continue de s'exécuter
Quand la Promise est résolue
await récupÚre la valeur et la fonction reprend
res contient maintenant la vraie valeur
On peut l'utiliser comme n'importe quelle variable
// 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 ... }
Gérer les erreurs comme en synchrone
Avec .then() â .catch()
Avec await â 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!
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
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.)
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
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
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
const a = await fetch('/api/a');
const b = await fetch('/api/b');
const c = await fetch('/api/c');
const d = await fetch('/api/d');
Total : ~2000ms
â ParallĂšle
const [a, b, c, d] = await Promise.all([
fetch('/api/a'),
fetch('/api/b'),
fetch('/api/c'),
fetch('/api/d'),
]);
Total : ~500ms
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
// 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
Si UNE SEULE Promise échoue, TOUT échoue
3 réussites + 1 échec = rejet complet
On perd les 3 résultats qui ont réussi!
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
Le service dépend du CONTRAT, pas de l'implémentation
Sans framework â juste un paramĂštre!
// â 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
// â 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.
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 :
đĄ Tant que l'implĂ©mentation respecte le contrat, le service fonctionne â c'est le principe!
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
// 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
// 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!
// 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
// 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
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 â 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Ă©!
â 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!
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
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
async/await + DI = du code propre et testable
Pratiquez avec les exercices!
async/await, try/catch, Promise.all(), injection de dépendances