Chapitre 3 : Programmation Asynchrone
Introduction
En JavaScript, de nombreuses opérations sont asynchrones :
- Appels API (fetch)
- Lecture de fichiers
- Timers (setTimeout)
- Accès à la base de données locale (IndexedDB)
Contrairement à PHP où le code s'exécute ligne par ligne, JavaScript peut lancer une opération et continuer sans attendre le résultat.
Le problème
// Ce code ne fait pas ce que vous pensez!
console.log("1. Début");
setTimeout(() => {
console.log("2. Dans le timeout");
}, 1000);
console.log("3. Fin");
// Affiche :
// "1. Début"
// "3. Fin"
// "2. Dans le timeout" (1 seconde plus tard)
Les Promises
Une Promise représente une valeur qui sera disponible dans le futur.
États d'une Promise
- pending : en attente
- fulfilled : résolue avec succès (contient une valeur)
- rejected : rejetée (contient une erreur)
Créer une Promise
const myPromise = new Promise((resolve, reject) => {
// Opération asynchrone simulée
setTimeout(() => {
const success = true;
if (success) {
resolve("Données reçues"); // Succès
} else {
reject(new Error("Échec")); // Erreur
}
}, 1000);
});
Consommer une Promise avec .then() et .catch()
myPromise
.then(result => {
console.log("Succès:", result);
})
.catch(error => {
console.log("Erreur:", error.message);
});
// Chaînage de .then()
fetchUser(1)
.then(user => fetchPosts(user.id))
.then(posts => console.log(posts))
.catch(error => console.log("Erreur:", error));
async/await
async/await est une syntaxe plus lisible pour travailler avec les Promises.
Syntaxe de base
// Fonction async
async function fetchData() {
const response = await fetch("https://api.example.com/data");
const data = await response.json();
return data;
}
// Arrow function async
const fetchData = async () => {
const response = await fetch("https://api.example.com/data");
const data = await response.json();
return data;
};
Règles importantes
awaitne peut être utilisé que dans une fonctionasync- Une fonction
asyncretourne toujours une Promise await"pause" l'exécution jusqu'à ce que la Promise soit résolue
Exemple comparatif
// Avec .then()
function getUserPosts(userId) {
return fetchUser(userId)
.then(user => fetchPosts(user.id))
.then(posts => {
console.log(posts);
return posts;
});
}
// Avec async/await (plus lisible)
async function getUserPosts(userId) {
const user = await fetchUser(userId);
const posts = await fetchPosts(user.id);
console.log(posts);
return posts;
}
Gestion des erreurs
Avec try/catch
async function fetchData() {
try {
const response = await fetch("https://api.example.com/data");
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error("Erreur:", error.message);
// Gérer l'erreur (afficher un message, retourner une valeur par défaut, etc.)
return null;
}
}
Pattern courant en React
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const loadData = async () => {
setLoading(true);
setError(null);
try {
const response = await fetch("/api/items");
const result = await response.json();
setData(result);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
Exécution parallèle vs séquentielle
Séquentiel (un après l'autre)
async function sequential() {
const user = await fetchUser(1); // Attend...
const posts = await fetchPosts(1); // Puis attend...
const comments = await fetchComments(1); // Puis attend...
// Temps total = temps1 + temps2 + temps3
}
Parallèle (en même temps)
async function parallel() {
// Lance les 3 requêtes en même temps
const [user, posts, comments] = await Promise.all([
fetchUser(1),
fetchPosts(1),
fetchComments(1)
]);
// Temps total = max(temps1, temps2, temps3)
}
Promise.all vs Promise.allSettled
// Promise.all - échoue si UNE promise échoue
try {
const results = await Promise.all([promise1, promise2, promise3]);
} catch (error) {
// Une des promises a échoué
}
// Promise.allSettled - attend toutes, même si certaines échouent
const results = await Promise.allSettled([promise1, promise2, promise3]);
// results = [
// { status: "fulfilled", value: ... },
// { status: "rejected", reason: Error },
// { status: "fulfilled", value: ... }
// ]
Comparaison avec PHP
// PHP - synchrone par défaut
$user = file_get_contents("https://api.example.com/user/1");
$posts = file_get_contents("https://api.example.com/posts/1");
// Chaque ligne attend que la précédente soit terminée
// JavaScript - asynchrone
const user = await fetch("https://api.example.com/user/1");
const posts = await fetch("https://api.example.com/posts/1");
// Similaire grâce à await, mais sous le capot c'est asynchrone
Pièges courants
1. Oublier await
// INCORRECT
async function getData() {
const data = fetch("/api/data"); // Oublié await!
console.log(data); // Promise { <pending> } - pas les données!
}
// CORRECT
async function getData() {
const response = await fetch("/api/data");
const data = await response.json();
console.log(data);
}
2. await dans une boucle (séquentiel non voulu)
// LENT - chaque itération attend la précédente
async function slow() {
for (const id of ids) {
const data = await fetchItem(id); // Séquentiel!
}
}
// RAPIDE - toutes les requêtes en parallèle
async function fast() {
const promises = ids.map(id => fetchItem(id));
const results = await Promise.all(promises);
}
3. Ne pas gérer les erreurs
// DANGEREUX - erreur non gérée
async function riskyCode() {
const data = await fetchData(); // Si ça échoue?
}
// SÛRE - erreur gérée
async function safeCode() {
try {
const data = await fetchData();
} catch (error) {
console.error("Erreur gérée:", error);
}
}
Exercices
Exercice 1 : Convertir en async/await
Convertir ce code utilisant .then() :
function getUser(id) {
return fetch(`/api/users/${id}`)
.then(response => response.json())
.then(user => {
console.log(user);
return user;
})
.catch(error => {
console.error("Erreur:", error);
return null;
});
}
Solution :
async function getUser(id) {
try {
const response = await fetch(`/api/users/${id}`);
const user = await response.json();
console.log(user);
return user;
} catch (error) {
console.error("Erreur:", error);
return null;
}
}
Exercice 2 : Parallélisation
Optimiser ce code pour exécuter les requêtes en parallèle :
async function loadDashboard(userId) {
const user = await fetchUser(userId);
const orders = await fetchOrders(userId);
const notifications = await fetchNotifications(userId);
return { user, orders, notifications };
}
Solution :
async function loadDashboard(userId) {
const [user, orders, notifications] = await Promise.all([
fetchUser(userId),
fetchOrders(userId),
fetchNotifications(userId)
]);
return { user, orders, notifications };
}
Points clés à retenir
- Promise : représente une valeur future (pending -> fulfilled/rejected)
- async/await : syntaxe lisible pour les Promises
- await : "pause" l'exécution jusqu'à résolution
- try/catch : gestion des erreurs avec async/await
- Promise.all : exécuter plusieurs Promises en parallèle
- Toujours gérer les erreurs avec try/catch
← Chapitre précédent | Retour au module | Chapitre suivant : Modules ES6 ->