TP - Programmation Asynchrone en JavaScript

Complétez les exercices dans votre éditeur (VSCode) et testez-les dans cette page

Instructions :

Téléchargez ce fichier, ouvrez-le dans VSCode, et complétez le code JavaScript à l'intérieur des balises <script> ( Lien de téléchargement )

Déposer le fichier exercices-js-async.html complété dans Classroom

PARTIE 1 : LES CALLBACKS AVEC SETTIMEOUT

Exercice 1.1 : Premier callback avec setTimeout

Objectif : Comprendre le mécanisme des callbacks en JavaScript

Consigne : Complétez la fonction recupererUtilisateur pour qu'elle simule un appel API qui prend 2 secondes. Utilisez setTimeout pour créer ce délai, puis appelez le callback avec les données de l'utilisateur.
TODO : Complétez le code dans la balise <script> ci-dessous
function recupererUtilisateur(id, callback) {
    console.log("Recherche de l'utilisateur " + id + "...");
    
    // TODO: Utilisez setTimeout pour simuler un délai de 2000ms
    // puis appelez le callback avec les données de l'utilisateur
    
    const utilisateur = {
        id: id,
        nom: "Alami",
        prenom: "Fatima",
        ville: "Casablanca"
    };
    
    // Votre code ici
}

Exercice 1.2 : Gestion d'erreur avec les callbacks

Objectif : Implémenter le pattern "error-first callback" utilisé dans Node.js

Consigne : Modifiez la fonction pour qu'elle gère les erreurs. Si l'ID est inférieur ou égal à 0, appelez le callback avec une erreur. Sinon, appelez-le avec null comme premier paramètre et les données en second.
function recupererUtilisateurAvecErreur(id, callback) {
    setTimeout(() => {
        // TODO: Si id <= 0, appelez callback(new Error("ID invalide"))
        // Sinon, appelez callback(null, utilisateur)
        
        const utilisateur = {
            id: id,
            nom: "Bennani",
            prenom: "Mohammed",
            email: "m.bennani@example.ma"
        };
        
        // Votre code ici
        
    }, 1000);
}
PARTIE 2 : XMLHTTPREQUEST ET CALLBACKS

Exercice 2.1 : Première requête AJAX

Objectif : Utiliser XMLHttpRequest pour faire une vraie requête HTTP

Consigne : Complétez la fonction pour récupérer un utilisateur depuis l'API JSONPlaceholder.
function recupererUtilisateurAPI(userId, callback) {
    const xhr = new XMLHttpRequest();
    
    // TODO: Configurez et envoyez la requête
    // 1. xhr.open('GET', url, true)
    // 2. xhr.onreadystatechange = function() { ... }
    // 3. Vérifiez readyState === 4 et status === 200
    // 4. Parsez la réponse avec JSON.parse()
    // 5. xhr.send()
    
    console.log("Envoi de la requête pour l'utilisateur " + userId);
    
    // Votre code ici
}
PARTIE 3 : LE PROBLÈME DU CALLBACK HELL

Exercice 3.1 : Cascade de callbacks

Objectif : Comprendre le problème du "callback hell"

Consigne : Utilisez les fonctions fournies pour créer une chaîne de callbacks :
  1. Récupérez l'utilisateur avec l'ID 1
  2. Avec cet utilisateur, récupérez ses posts
  3. Avec le premier post, récupérez ses commentaires
  4. Affichez chaque résultat dans la console
Les fonctions recupererUtilisateur, recupererPosts et recupererCommentaires sont déjà définies. Vous devez juste les appeler en cascade.
// Fonctions déjà définies (ne pas modifier)
function recupererUtilisateur(id, callback) {
    setTimeout(() => {
        callback({
            id: id,
            nom: "El Idrissi",
            prenom: "Amina",
            ville: "Rabat"
        });
    }, 1000);
}

function recupererPosts(userId, callback) {
    setTimeout(() => {
        callback([
            { id: 1, titre: "Introduction à Node.js", userId: userId },
            { id: 2, titre: "Les Promises en JavaScript", userId: userId }
        ]);
    }, 1000);
}

function recupererCommentaires(postId, callback) {
    setTimeout(() => {
        callback([
            { id: 1, texte: "Excellent article!", auteur: "Khadija" },
            { id: 2, texte: "Très instructif", auteur: "Hassan" }
        ]);
    }, 1000);
}

function demonstrationCallbackHell() {
    console.log("Début de la récupération des données...");
    
    // TODO: Appelez les fonctions en cascade
    // Attention à l'indentation qui va créer la "pyramide de l'enfer"
    
    // Votre code ici
}
PARTIE 4 : LES PROMESSES AVEC FETCH API

Exercice 4.1 : Introduction à Fetch

Objectif : Découvrir l'API Fetch qui retourne des Promesses

Consigne : Utilisez l'API Fetch pour récupérer un utilisateur. Fetch est plus moderne que XMLHttpRequest :
function recupererUtilisateurAvecFetch(userId) {
    // TODO: Utilisez fetch() pour récupérer les données
    // URL: https://jsonplaceholder.typicode.com/users/{userId}
    
    // return fetch(...) 
    //     .then(response => ...)
    //     .then(data => ...)
    
    // Votre code ici
}

Exercice 5.1 : Chaînage de Promesses

Objectif : Résoudre le callback hell avec les Promesses

Consigne : Refactorisez l'exercice 3.1 en utilisant les versions Promise des fonctions. Chaînez les .then() pour obtenir le même résultat mais avec un code plus lisible.
// Versions Promise des fonctions (déjà définies)
function recupererUtilisateurPromise(id) {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve({
                id: id,
                nom: "Tazi",
                prenom: "Omar",
                profession: "Ingénieur"
            });
        }, 1000);
    });
}

function recupererPostsPromise(userId) {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve([
                { id: 1, titre: "Les bases de MongoDB", userId },
                { id: 2, titre: "Redis pour les débutants", userId }
            ]);
        }, 1000);
    });
}

function recupererCommentairesPromise(postId) {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve([
                { id: 1, texte: "Super article!", auteur: "Youssef" },
                { id: 2, texte: "Merci pour le partage", auteur: "Salma" }
            ]);
        }, 1000);
    });
}

function demonstrationPromiseChain() {
    console.log("Début avec les Promesses...");
    
    // TODO: Chaînez les promesses avec .then()
    // recupererUtilisateurPromise(1)
    //     .then(utilisateur => { ... })
    //     .then(...)
    //     .catch(erreur => { ... })
    
    // Votre code ici
}
PARTIE 5 : REQUÊTES PARALLÈLES AVEC PROMISE.ALL

Exercice 6.1 : Optimisation avec Promise.all

Objectif : Exécuter plusieurs requêtes en parallèle pour gagner du temps

Consigne : Utilisez Promise.all pour récupérer plusieurs profils d'utilisateurs simultanément. Comparez le temps d'exécution avec une approche séquentielle.
function recupererProfil(userId) {
    return new Promise((resolve) => {
        const delai = Math.random() * 2000 + 500; // Entre 500ms et 2500ms
        setTimeout(() => {
            const noms = ["Zahiri", "Mansouri", "Berrada", "Alaoui", "Benjelloun"];
            const prenoms = ["Nadia", "Leila", "Karim", "Sofia", "Ahmed"];
            resolve({
                id: userId,
                nom: noms[userId - 1],
                prenom: prenoms[userId - 1],
                delai: delai
            });
        }, delai);
    });
}

function demonstrationPromiseAll() {
    // TODO: Implémentez deux versions :
    
    // Version 1 : Séquentielle (mauvaise performance)
    console.log("Version séquentielle :");
    console.time("Durée séquentielle");
    // Récupérez les profils 1, 2, 3 l'un après l'autre
    
    // Version 2 : Parallèle avec Promise.all (bonne performance)
    console.log("\nVersion parallèle :");
    console.time("Durée parallèle");
    // Récupérez les profils 1, 2, 3 simultanément
    
    // Votre code ici
}
PARTIE 6 : ASYNC/AWAIT - LA SYNTAXE MODERNE

Exercice 7.1 : Introduction à async/await

Objectif : Utiliser async/await pour écrire du code asynchrone qui ressemble à du code synchrone

Consigne : Créez une fonction async qui récupère et affiche un tableau de bord complet. Utilisez await pour attendre chaque opération et try/catch pour gérer les erreurs.
// Fonction utilitaire (déjà définie)
function obtenirDonnees(type, id) {
    const donnees = {
        utilisateur: { 
            id: 1, 
            nom: "Rachidi", 
            prenom: "Amal", 
            role: "Chef de projet" 
        },
        projet: { 
            id: 1, 
            nom: "Migration Cloud", 
            status: "En cours",
            progression: 75
        },
        taches: [
            { id: 1, titre: "Configuration serveurs", fait: true },
            { id: 2, titre: "Migration base de données", fait: true },
            { id: 3, titre: "Tests de performance", fait: false },
            { id: 4, titre: "Documentation", fait: false }
        ]
    };
    
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (donnees[type]) {
                resolve(donnees[type]);
            } else {
                reject(new Error("Type de données inconnu"));
            }
        }, 800);
    });
}

// TODO: Créez la fonction async
async function afficherTableauDeBord() {
    console.log("Chargement du tableau de bord...");
    
    try {
        // TODO: Utilisez await pour récupérer :
        // 1. L'utilisateur
        // 2. Le projet
        // 3. Les tâches
        // Puis affichez un résumé formaté
        
        // Votre code ici
        
    } catch (erreur) {
        console.error("Erreur lors du chargement:", erreur.message);
    }
}

Exercice 8.1 : Gestion d'erreur robuste avec Fetch

Objectif : Implémenter une gestion d'erreur complète pour les appels API

Consigne : Créez une fonction qui gère tous les types d'erreurs possibles :
async function fetchAvecGestionErreur(url) {
    try {
        // TODO: Implémentez la logique complète
        // 1. Faire la requête avec fetch
        // 2. Vérifier response.ok
        // 3. Parser le JSON
        // 4. Gérer chaque type d'erreur différemment
        
        // Votre code ici
        
    } catch (erreur) {
        // TODO: Distinguer les types d'erreurs
        // TypeError = erreur réseau
        // Autres = erreurs HTTP ou JSON
        
        throw erreur;
    }
}

// Fonction de test
async function testerGestionErreurs() {
    const urls = [
        'https://jsonplaceholder.typicode.com/users/1',     // OK
        'https://jsonplaceholder.typicode.com/users/9999',  // 404
        'https://api-inexistante-xyz123.com/test'          // Erreur réseau
    ];
    
    for (const url of urls) {
        console.log(`\nTest avec : ${url}`);
        try {
            const donnees = await fetchAvecGestionErreur(url);
            console.log('✓ Succès:', donnees);
        } catch (erreur) {
            console.error('✗ Erreur:', erreur.message);
        }
    }
}
EXERCICE FINAL : APPLICATION COMPLÈTE

Exercice 9.1 : Dashboard temps réel

Objectif : Créer une application complète utilisant tous les concepts appris

Consigne : Implémentez un dashboard qui :
Cet exercice combine : setTimeout/setInterval, Promises, async/await, et gestion d'erreur

Tableau de bord en temps réel

Utilisateurs actifs

--

Messages aujourd'hui

--

Temps de réponse

--

// Variables globales pour le dashboard
let intervalId = null;
let isRunning = false;

// Simule une API qui retourne des statistiques
async function obtenirStatistiques() {
    const delai = Math.random() * 1000 + 200;
    const debut = Date.now();
    
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            // Simule une erreur 10% du temps
            if (Math.random() < 0.1) {
                reject(new Error("Erreur serveur"));
            } else {
                resolve({
                    utilisateursActifs: Math.floor(Math.random() * 100) + 50,
                    messagesAujourdhui: Math.floor(Math.random() * 1000) + 200,
                    tempsReponse: Date.now() - debut
                });
            }
        }, delai);
    });
}

// TODO: Implémentez la fonction de mise à jour
async function mettreAJourDashboard() {
    try {
        // TODO:
        // 1. Appelez obtenirStatistiques()
        // 2. Mettez à jour les éléments DOM
        // 3. Gérez les erreurs gracieusement
        
        // Votre code ici
        
    } catch (erreur) {
        // TODO: Affichez l'erreur sans crasher l'application
    }
}

// TODO: Implémentez la fonction pour démarrer le dashboard
function demarrerDashboard() {
    // TODO:
    // 1. Vérifiez si déjà en cours
    // 2. Lancez une première mise à jour
    // 3. Configurez setInterval pour mise à jour automatique
    // 4. Mettez à jour l'état des boutons
    
    // Votre code ici
}

// TODO: Implémentez la fonction pour arrêter le dashboard
function arreterDashboard() {
    // TODO:
    // 1. Arrêtez l'interval
    // 2. Réinitialisez les variables
    // 3. Mettez à jour l'interface
    
    // Votre code ici
}

// Fonction bonus : rafraîchissement manuel
function rafraichirMaintenant() {
    if (isRunning) {
        mettreAJourDashboard();
    } else {
        console.log("Le dashboard doit être démarré d'abord");
    }
}