0%
JavaScript Moderne : Promises et Async/Await

Promises & Async

Programmation Asynchrone Moderne

10-15 min

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

  1. 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)
));
  1. 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

Lien copié !