Inhoud

Chapitre 3 : Programmation Asynchrone

Introduction

En JavaScript, de nombreuses opérations sont asynchrones :

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

snippet.javascript
// 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

Créer une Promise

snippet.javascript
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()

snippet.javascript
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

snippet.javascript
// 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

  1. await ne peut être utilisé que dans une fonction async
  2. Une fonction async retourne toujours une Promise
  3. await “pause” l'exécution jusqu'à ce que la Promise soit résolue

Exemple comparatif

snippet.javascript
// 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

snippet.javascript
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

snippet.javascript
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)

snippet.javascript
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)

snippet.javascript
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

snippet.javascript
// 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

snippet.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
snippet.javascript
// 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

snippet.javascript
// 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)

snippet.javascript
// 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

snippet.javascript
// 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() :

snippet.javascript
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 :

snippet.javascript
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 :

snippet.javascript
async function loadDashboard(userId) {
    const user = await fetchUser(userId);
    const orders = await fetchOrders(userId);
    const notifications = await fetchNotifications(userId);
 
    return { user, orders, notifications };
}

Solution :

snippet.javascript
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

  1. Promise : représente une valeur future (pending → fulfilled/rejected)
  2. async/await : syntaxe lisible pour les Promises
  3. await : “pause” l'exécution jusqu'à résolution
  4. try/catch : gestion des erreurs avec async/await
  5. Promise.all : exécuter plusieurs Promises en parallèle
  6. Toujours gérer les erreurs avec try/catch

← Chapitre précédent | Retour au module | Chapitre suivant : Modules ES6 →