Promises & Async
Modern Asynchronous Programming
Modern JavaScript: Promises and Async/Await
Asynchronous programming is at the heart of modern JavaScript. Promises and async/await are essential tools for handling asynchronous operations in an elegant and maintainable way.
Promises
A Promise is an object that represents the eventual completion (or failure) of an asynchronous operation.
Creating a 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));
Promise States
A Promise can be in one of the following three states:
- Pending: Initial state, neither fulfilled nor rejected
- Fulfilled: The operation succeeded
- Rejected: The operation failed
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" }
Promise Chaining
Promises can be chained to run sequential operations:
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));
Useful Promise Methods
Promise.all()
Waits for all promises to be fulfilled:
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()
Returns the first promise to be fulfilled or rejected:
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()
Waits for all promises to settle (fulfilled or rejected):
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 is a more elegant syntax for working with Promises.
Async Functions
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));
With Arrow Functions
const obtenirDonnees = async () => {
const utilisateur = await obtenirUtilisateur(1);
const commandes = await obtenirCommandes(utilisateur);
return commandes;
};
Parallel Execution with 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;
}
}
Asynchronous Loops
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 and Best Practices
Retry Pattern
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 for 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));
Error Handling
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;
}
}
Practical Use Cases
HTTP Requests with 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;
}
}
Loading 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;
}
}
Caching with 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();
});
}
Practice Exercises
- Simulating a queue
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)
));
- Concurrent request manager
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
Promises and async/await are fundamental tools for handling asynchronicity in modern JavaScript:
- Promises offer a structured approach to handling asynchronous operations
- async/await simplifies the syntax and makes code more readable
- Patterns like retry, timeout, and caching are essential for robust applications
- Proper error handling is crucial for application reliability
In the next tutorial, we’ll explore ES modules and how to organize your JavaScript code effectively.
Need to brush up on object-oriented programming? Check out our previous tutorial on OOP.
Comments
Comments are powered by GitHub Discussions
Sign in with GitHub to join the discussion