Promises & Async
Programmation Asynchrone Moderne
JavaScript Moderne : Promises et Async/Await
La programmation asynchrone est au cœur du JavaScript moderne. Les Promises et async/await sont des outils essentiels pour gérer les opérations asynchrones de manière élégante et maintenable.
Les Promises
Une Promise est un objet qui représente l’achèvement (ou l’échec) éventuel d’une opération asynchrone.
Création d’une Promise
const maPromesse = new Promise((resolve, reject) => {
// Opération asynchrone
setTimeout(() => {
const succes = true;
if (succes) {
resolve("Opération réussie !");
} else {
reject(new Error("L'opération a échoué"));
}
}, 1000);
});
// Utilisation
maPromesse
.then(resultat => console.log(resultat))
.catch(erreur => console.error(erreur));
États d’une Promise
Une Promise peut être dans l’un des trois états suivants :
- Pending : État initial, ni accomplie ni rejetée
- Fulfilled : Opération réussie
- Rejected : Opération échouée
const promessePending = new Promise(() => {});
console.log(promessePending); // Promise { <pending> }
const promesseFulfilled = Promise.resolve("Succès");
console.log(promesseFulfilled); // Promise { <fulfilled>: "Succès" }
const promesseRejected = Promise.reject("Erreur");
console.log(promesseRejected); // Promise { <rejected>: "Erreur" }
Chaînage de Promises
Les Promises peuvent être chaînées pour exécuter des opérations séquentielles :
function obtenirUtilisateur(id) {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ id, nom: "Alice" });
}, 1000);
});
}
function obtenirCommandes(utilisateur) {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
utilisateur,
commandes: [
{ id: 1, produit: "Livre" },
{ id: 2, produit: "Ordinateur" }
]
});
}, 1000);
});
}
// Chaînage
obtenirUtilisateur(1)
.then(utilisateur => obtenirCommandes(utilisateur))
.then(resultat => console.log(resultat))
.catch(erreur => console.error(erreur));
Méthodes utiles avec les Promises
Promise.all()
Attend que toutes les promises soient résolues :
const promise1 = Promise.resolve(3);
const promise2 = new Promise(resolve => setTimeout(() => resolve(42), 1000));
const promise3 = fetch('https://api.example.com/data');
Promise.all([promise1, promise2, promise3])
.then(resultats => {
console.log(resultats); // [3, 42, Response]
})
.catch(erreur => {
console.error("Une des promises a échoué:", erreur);
});
Promise.race()
Retourne la première promise résolue ou rejetée :
const promise1 = new Promise(resolve => setTimeout(() => resolve("un"), 1000));
const promise2 = new Promise(resolve => setTimeout(() => resolve("deux"), 500));
Promise.race([promise1, promise2])
.then(resultat => console.log(resultat)); // "deux"
Promise.allSettled()
Attend que toutes les promises soient terminées (résolues ou rejetées) :
const promises = [
Promise.resolve(1),
Promise.reject("erreur"),
Promise.resolve(3)
];
Promise.allSettled(promises)
.then(resultats => {
resultats.forEach(resultat => {
if (resultat.status === "fulfilled") {
console.log("Succès:", resultat.value);
} else {
console.log("Échec:", resultat.reason);
}
});
});
Async/Await
async/await est une syntaxe plus élégante pour travailler avec les Promises.
Fonctions asynchrones
async function obtenirDonnees() {
try {
const utilisateur = await obtenirUtilisateur(1);
const commandes = await obtenirCommandes(utilisateur);
return commandes;
} catch (erreur) {
console.error("Erreur:", erreur);
throw erreur;
}
}
// Utilisation
obtenirDonnees()
.then(resultat => console.log(resultat))
.catch(erreur => console.error(erreur));
Avec les fonctions fléchées
const obtenirDonnees = async () => {
const utilisateur = await obtenirUtilisateur(1);
const commandes = await obtenirCommandes(utilisateur);
return commandes;
};
Parallélisation avec async/await
async function obtenirTout() {
try {
// Exécution parallèle
const [utilisateurs, produits] = await Promise.all([
obtenirUtilisateurs(),
obtenirProduits()
]);
return { utilisateurs, produits };
} catch (erreur) {
console.error("Erreur:", erreur);
throw erreur;
}
}
Boucles asynchrones
async function traiterUtilisateurs(utilisateurs) {
// Séquentiel
for (const utilisateur of utilisateurs) {
await envoyerEmail(utilisateur);
}
// Parallèle
const promesses = utilisateurs.map(utilisateur => envoyerEmail(utilisateur));
await Promise.all(promesses);
}
Patterns et bonnes pratiques
Pattern de retry
async function avecRetry(fn, maxTentatives = 3) {
let dernierErreur;
for (let tentative = 1; tentative <= maxTentatives; tentative++) {
try {
return await fn();
} catch (erreur) {
dernierErreur = erreur;
console.log(`Tentative ${tentative} échouée`);
if (tentative < maxTentatives) {
await new Promise(resolve =>
setTimeout(resolve, 1000 * tentative)
);
}
}
}
throw dernierErreur;
}
// Utilisation
const fetchAvecRetry = () =>
avecRetry(() => fetch('https://api.example.com/data'));
Timeout pour les Promises
function avecTimeout(promesse, delai) {
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error('Timeout')), delai);
});
return Promise.race([promesse, timeoutPromise]);
}
// Utilisation
avecTimeout(fetch('https://api.example.com/data'), 5000)
.then(response => response.json())
.catch(erreur => console.error('Erreur ou timeout:', erreur));
Gestion des erreurs
async function gererErreurs() {
try {
const donnees = await fetch('https://api.example.com/data');
if (!donnees.ok) {
throw new Error(`HTTP error! status: ${donnees.status}`);
}
const resultat = await donnees.json();
return resultat;
} catch (erreur) {
if (erreur instanceof TypeError) {
console.error('Erreur réseau:', erreur);
} else if (erreur.name === 'AbortError') {
console.error('Requête annulée:', erreur);
} else {
console.error('Autre erreur:', erreur);
}
throw erreur;
}
}
Cas d’utilisation pratiques
Requêtes HTTP avec fetch
async function obtenirDonneesAPI() {
try {
const response = await fetch('https://api.example.com/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ key: 'value' })
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error('Erreur:', error);
throw error;
}
}
Chargement d’images
function chargerImage(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = () => reject(new Error(`Échec du chargement de ${url}`));
img.src = url;
});
}
async function chargerGalerie(urls) {
try {
const images = await Promise.all(
urls.map(url => chargerImage(url))
);
return images;
} catch (erreur) {
console.error('Erreur de chargement:', erreur);
throw erreur;
}
}
Cache avec Promises
class CachePromise {
constructor() {
this.cache = new Map();
}
async get(key, fetchFn) {
if (this.cache.has(key)) {
return this.cache.get(key);
}
const promise = fetchFn();
this.cache.set(key, promise);
try {
await promise;
return promise;
} catch (erreur) {
this.cache.delete(key);
throw erreur;
}
}
}
// Utilisation
const cache = new CachePromise();
async function obtenirUtilisateur(id) {
return cache.get(`utilisateur-${id}`, async () => {
const response = await fetch(`/api/utilisateurs/${id}`);
return response.json();
});
}
Exercices pratiques
- Simulation d’une file d’attente
class FileAttente {
constructor() {
this.taches = [];
}
ajouterTache(tache) {
return new Promise((resolve, reject) => {
this.taches.push({ tache, resolve, reject });
if (this.taches.length === 1) {
this.executerSuivant();
}
});
}
async executerSuivant() {
if (this.taches.length === 0) return;
const { tache, resolve, reject } = this.taches[0];
try {
const resultat = await tache();
resolve(resultat);
} catch (erreur) {
reject(erreur);
} finally {
this.taches.shift();
this.executerSuivant();
}
}
}
// Utilisation
const file = new FileAttente();
file.ajouterTache(() => new Promise(resolve =>
setTimeout(() => resolve("Tâche 1"), 1000)
));
file.ajouterTache(() => new Promise(resolve =>
setTimeout(() => resolve("Tâche 2"), 500)
));
- Gestionnaire de requêtes concurrentes
class GestionnaireRequetes {
constructor(maxConcurrent = 3) {
this.maxConcurrent = maxConcurrent;
this.actives = 0;
this.file = [];
}
async ajouter(fn) {
if (this.actives >= this.maxConcurrent) {
await new Promise(resolve => this.file.push(resolve));
}
this.actives++;
try {
return await fn();
} finally {
this.actives--;
if (this.file.length > 0) {
const suivant = this.file.shift();
suivant();
}
}
}
}
// Utilisation
const gestionnaire = new GestionnaireRequetes(2);
async function effectuerRequetes() {
const urls = ['url1', 'url2', 'url3', 'url4', 'url5'];
const promesses = urls.map(url =>
gestionnaire.ajouter(() => fetch(url))
);
return Promise.all(promesses);
}
Conclusion
Les Promises et async/await sont des outils fondamentaux pour gérer l’asynchronicité en JavaScript moderne :
- Les Promises offrent une approche structurée pour gérer les opérations asynchrones
- async/await simplifie la syntaxe et rend le code plus lisible
- Les patterns comme le retry, le timeout et le cache sont essentiels pour des applications robustes
- La gestion appropriée des erreurs est cruciale pour la fiabilité des applications
Dans le prochain tutoriel, nous explorerons les modules ES et comment organiser efficacement votre code JavaScript.
Besoin de réviser la programmation orientée objet ? Consultez notre tutoriel précédent sur la POO.
Commentaires
Les commentaires sont alimentés par GitHub Discussions
Connectez-vous avec GitHub pour participer à la discussion