Maîtriser le code qui ne bloque pas
Callbacks • Promises • Async/Await • Fetch API
Formation DWWM - 2026
Comprendre le concept fondamental
L'asynchrone permet d'exécuter du code sans attendre qu'une opération longue se termine.
Restaurant SYNCHRONE (bloquant) :
Le serveur prend la commande du client 1, va en cuisine, attend que le plat soit prêt (30 min), revient, sert, puis va voir le client 2. Pendant ce temps, les autres clients attendent et s'impatientent ! ❌
Restaurant ASYNCHRONE (non-bloquant) :
Le serveur prend la commande du client 1, la transmet en cuisine, et IMMÉDIATEMENT va prendre la commande du client 2, puis du 3, etc. Quand un plat est prêt, le serveur revient le servir. Tout le monde est content ! ✅
L'asynchrone en JavaScript : Ton code peut continuer à s'exécuter pendant qu'une opération longue (requête réseau, lecture de fichier...) est en cours.
Certaines opérations prennent du temps et doivent être asynchrones :
| Opération | Temps | Pourquoi c'est long ? |
|---|---|---|
| Requête HTTP | 100ms - 5s+ | Dépend du réseau et du serveur |
| Lecture de fichier | 10ms - 1s+ | Dépend de la taille et du disque |
| Accès base de données | 10ms - 2s+ | Requêtes complexes, connexion réseau |
| setTimeout / setInterval | Variable | Délai intentionnel |
| Géolocalisation | 1s - 5s+ | GPS, permissions utilisateur |
⚠️ Problème : Si ces opérations étaient SYNCHRONES (bloquantes), toute l'application se figerait pendant l'attente. L'utilisateur ne pourrait rien faire ! La page serait complètement bloquée !
✅ Solution : Les faire en ASYNCHRONE ! Le code continue de s'exécuter, et quand l'opération est terminée, on exécute une fonction de rappel (callback).
console.log("1. Début");
// Opération longue (bloque tout !)
attendreBloquant(3000);
console.log("2. Fin");
// Résultat :
// 1. Début
// ... attend 3 secondes ...
// 2. Fin
❌ Le code s'arrête complètement pendant 3 secondes
console.log("1. Début");
// Opération longue (ne bloque pas !)
setTimeout(() => {
console.log("3. Après 3 sec");
}, 3000);
console.log("2. Fin");
// Résultat :
// 1. Début
// 2. Fin
// ... 3 secondes plus tard ...
// 3. Après 3 sec
✅ Le code continue immédiatement
Imaginons qu'on charge des données depuis un serveur de manière synchrone...
console.log("1. Démarrage de l'app");
// Charger les données (BLOQUANT - 5 secondes)
const data = chargerDonneesBloquant("https://api.example.com");
console.log("2. Données:", data);
console.log("3. App prête !");
// Ce qui se passe :
// 1. "Démarrage de l'app" s'affiche
// 2. ❌ TOUT SE FIGE PENDANT 5 SECONDES
// - Impossible de cliquer sur un bouton
// - Impossible de scroller
// - L'interface est MORTE
// 3. Après 5 sec, les données arrivent
// 4. Le reste du code s'exécute
Fonctions de rappel
Un callback (fonction de rappel) est une fonction passée en paramètre qui sera appelée plus tard, quand l'opération asynchrone sera terminée.
Tu commandes une pizza par téléphone. Le pizzaïolo te dit : "Ça prendra 30 minutes. Donne-moi ton numéro (callback), je t'appelle quand c'est prêt".
Pendant ces 30 minutes, tu ne restes pas au téléphone à attendre (synchrone) ! Tu raccroches et tu fais autre chose. Quand la pizza est prête, le pizzaïolo t'appelle (exécute le callback).
setTimeout exécute une fonction après un délai. C'est l'exemple parfait de callback !
console.log("1. Début");
setTimeout(() => {
console.log("3. Exécuté après 2 secondes");
}, 2000);
console.log("2. Fin");
// Affichage :
// 1. Début
// 2. Fin
// ... attend 2 secondes ...
// 3. Exécuté après 2 secondes
t=0ms : console.log("Début")
t=1ms : setTimeout programmé (callback enregistré)
t=2ms : console.log("Fin")
t=2000ms : Le callback s'exécute → console.log("Exécuté après 2 secondes")
Décomposons étape par étape ce qui se passe avec setTimeout.
console.log("A");
setTimeout(() => {
console.log("B");
}, 1000);
console.log("C");
Exemple plus concret : simuler le chargement de données.
// Fonction asynchrone qui prend un callback
function chargerUtilisateur(id, callback) {
console.log(`Chargement de l'utilisateur ${id}...`);
// Simule un délai réseau de 2 secondes
setTimeout(() => {
const utilisateur = {
id: id,
nom: "Marie Dupont",
email: "marie@example.com"
};
// Quand les données sont prêtes, on appelle le callback
callback(utilisateur);
}, 2000);
}
// Utilisation
console.log("Début");
chargerUtilisateur(123, (user) => {
console.log("Utilisateur chargé:", user);
console.log("Nom:", user.nom);
});
console.log("Fin (sans attendre)");
// Affichage :
// Début
// Chargement de l'utilisateur 123...
// Fin (sans attendre)
// ... 2 secondes plus tard ...
// Utilisateur chargé: {id: 123, nom: "Marie Dupont", ...}
// Nom: Marie Dupont
Quand on a plusieurs opérations asynchrones qui dépendent les unes des autres, les callbacks s'imbriquent et le code devient illisible !
// ❌ CALLBACK HELL (Pyramide de la mort)
chargerUtilisateur(123, (utilisateur) => {
console.log("User:", utilisateur.nom);
chargerPosts(utilisateur.id, (posts) => {
console.log("Posts:", posts.length);
chargerComments(posts[0].id, (comments) => {
console.log("Comments:", comments.length);
chargerLikes(comments[0].id, (likes) => {
console.log("Likes:", likes);
// Et ça continue... 😱
// Le code part vers la droite !
});
});
});
});
// Problèmes :
// ❌ Difficile à lire (pyramide)
// ❌ Difficile à maintenir
// ❌ Gestion d'erreurs complexe
// ❌ On appelle ça "Callback Hell" ou "Pyramid of Doom"
⚠️ C'est pour résoudre ce problème que les Promises ont été créées !
Promesses d'avenir
Une Promise (promesse) est un objet qui représente le résultat futur d'une opération asynchrone.
Tu commandes un burger au McDo. On te donne un ticket avec le numéro 42. Ce ticket est une PROMESSE que tu recevras ton burger.
Le ticket a 3 états possibles :
| État | Signification | On peut encore changer ? |
|---|---|---|
| PENDING | En attente, opération en cours | ✅ Oui |
| FULFILLED | Terminée avec succès | ❌ Non, état final |
| REJECTED | Terminée avec erreur | ❌ Non, état final |
⚠️ Important : Une fois qu'une Promise passe à FULFILLED ou REJECTED, elle ne peut PLUS changer d'état. C'est définitif !
Syntaxe pour créer une nouvelle Promise :
const promesse = new Promise((resolve, reject) => {
// Simule une opération qui prend 2 secondes
setTimeout(() => {
const succes = true;
if (succes) {
resolve("Données chargées !"); // ✅ Succès
} else {
reject("Erreur de chargement"); // ❌ Erreur
}
}, 2000);
});
console.log(promesse); // Promise {}
// Après 2 secondes :
// Promise {: "Données chargées !"}
then() s'exécute quand la Promise est résolue (fulfilled).
const promesse = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Succès !");
}, 2000);
});
promesse.then((resultat) => {
console.log("Résultat:", resultat);
});
// Après 2 secondes :
// Résultat: Succès !
function chargerUtilisateur(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
const utilisateur = {
id: id,
nom: "Marie",
email: "marie@example.com"
};
resolve(utilisateur);
}, 2000);
});
}
chargerUtilisateur(123).then((user) => {
console.log("User chargé:", user.nom);
});
// Après 2s : User chargé: Marie
catch() s'exécute quand la Promise est rejetée (rejected).
const promesse = new Promise((resolve, reject) => {
setTimeout(() => {
reject("Erreur serveur !"); // ❌ Rejette la promesse
}, 2000);
});
promesse
.then((resultat) => {
console.log("Succès:", resultat);
})
.catch((erreur) => {
console.log("Erreur:", erreur);
});
// Après 2 secondes :
// Erreur: Erreur serveur !
function chargerUtilisateur(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (id <= 0) {
reject("ID invalide"); // ❌ Erreur
return;
}
const utilisateur = {
id: id,
nom: "Marie"
};
resolve(utilisateur); // ✅ Succès
}, 1000);
});
}
// Cas de succès
chargerUtilisateur(123)
.then(user => console.log("User:", user.nom))
.catch(err => console.log("Erreur:", err));
// User: Marie
// Cas d'erreur
chargerUtilisateur(-5)
.then(user => console.log("User:", user.nom))
.catch(err => console.log("Erreur:", err));
// Erreur: ID invalide
On peut chaîner plusieurs .then() pour des opérations successives. Beaucoup plus lisible que les callbacks !
chargerUser(123, user => {
chargerPosts(user.id, posts => {
chargerComments(posts[0].id,
comments => {
console.log(comments);
});
});
});
chargerUser(123)
.then(user => chargerPosts(user.id))
.then(posts => chargerComments(posts[0].id))
.then(comments => console.log(comments))
.catch(err => console.log(err));
✅ Chaque .then() peut retourner une nouvelle Promise, permettant de chaîner les opérations de manière lisible et linéaire !
finally() s'exécute dans tous les cas, que la Promise soit résolue ou rejetée.
chargerUtilisateur(123)
.then(user => {
console.log("Succès:", user);
})
.catch(err => {
console.log("Erreur:", err);
})
.finally(() => {
console.log("Opération terminée (succès ou erreur)");
});
// Si succès :
// Succès: {id: 123, nom: "Marie"}
// Opération terminée (succès ou erreur)
// Si erreur :
// Erreur: Utilisateur introuvable
// Opération terminée (succès ou erreur)
// Afficher un indicateur de chargement
afficherLoader();
chargerDonnees()
.then(data => afficherDonnees(data))
.catch(err => afficherErreur(err))
.finally(() => {
cacherLoader(); // Toujours cacher le loader
});
Utilité : Parfait pour du code de nettoyage qui doit s'exécuter peu importe le résultat (fermer connexion, cacher loader, etc.)
La syntaxe moderne et élégante
async/await est une syntaxe moderne qui rend le code asynchrone aussi lisible que du code synchrone.
Promises avec .then() : C'est comme lire un livre où il faut tourner les pages dans le bon ordre, mais les chapitres ne se suivent pas naturellement.
Async/await : C'est comme regarder un film où tout se déroule de manière fluide et séquentielle. Tu suis l'histoire ligne par ligne, naturellement !
chargerUser(123)
.then(user => {
return chargerPosts(user.id);
})
.then(posts => {
console.log(posts);
})
.catch(err => {
console.log(err);
});
try {
const user = await chargerUser(123);
const posts = await chargerPosts(user.id);
console.log(posts);
} catch (err) {
console.log(err);
}
✅ C'est la MÊME chose, juste écrit différemment ! Async/await est du sucre syntaxique par-dessus les Promises.
async devant une fonction signifie : "Cette fonction retourne TOUJOURS une Promise".
function saluer() {
return "Bonjour";
}
const resultat = saluer();
console.log(resultat); // "Bonjour" (string)
async function saluer() {
return "Bonjour";
}
const resultat = saluer();
console.log(resultat); // Promise {: "Bonjour"}
// Pour obtenir la valeur
saluer().then(msg => console.log(msg)); // "Bonjour"
// Ces deux fonctions sont équivalentes !
async function obtenir() {
return 42;
}
function obtenir() {
return Promise.resolve(42);
}
// Les deux retournent : Promise {: 42}
await = "Attend que cette Promise se résolve, puis continue".
⚠️ RÈGLE ABSOLUE : On ne peut utiliser await QUE dans une fonction async !
// ❌ ERREUR : await sans async
function maFonction() {
const result = await promesse(); // ❌ Erreur de syntaxe !
}
// ✅ CORRECT : await dans une fonction async
async function maFonction() {
const result = await promesse(); // ✅ OK
}
function attendreDeuxSecondes() {
return new Promise(resolve => {
setTimeout(() => resolve("Terminé !"), 2000);
});
}
async function tester() {
console.log("Début");
const resultat = await attendreDeuxSecondes();
// ⏸️ Le code S'ARRÊTE ICI et attend 2 secondes
console.log(resultat);
console.log("Fin");
}
tester();
// Affichage :
// Début
// ... attend 2 secondes ...
// Terminé !
// Fin
Décomposons ligne par ligne ce qui se passe avec await.
async function chargerDonnees() {
console.log("1. Début");
const user = await chargerUtilisateur(123);
console.log("2. User:", user.nom);
console.log("3. Fin");
}
await "déballé" la Promise et te donne directement la valeur, comme si c'était du code synchrone !
Avec async/await, on utilise try/catch pour gérer les erreurs (comme en Java, C#, etc.).
async function charger() {
const user = await chargerUtilisateur(123);
// Si chargerUtilisateur échoue → ERREUR NON GÉRÉE !
console.log(user);
}
async function charger() {
try {
const user = await chargerUtilisateur(123);
console.log("Succès:", user);
} catch (erreur) {
console.log("Erreur:", erreur);
}
}
// Si succès → "Succès: {id: 123, nom: "Marie"}"
// Si erreur → "Erreur: Utilisateur introuvable"
async function charger() {
try {
afficherLoader();
const user = await chargerUtilisateur(123);
afficherUser(user);
} catch (erreur) {
afficherErreur(erreur);
} finally {
cacherLoader(); // Toujours exécuté
}
}
On peut utiliser plusieurs await dans la même fonction. Chaque await ATTEND que la Promise précédente se résolve.
async function chargerTout() {
console.log("Début");
const user = await chargerUtilisateur(123);
console.log("User chargé:", user.nom);
const posts = await chargerPosts(user.id);
console.log("Posts chargés:", posts.length);
const comments = await chargerComments(posts[0].id);
console.log("Comments chargés:", comments.length);
console.log("Tout est chargé !");
}
// Affichage (étape par étape) :
// Début
// ... attend chargerUtilisateur ...
// User chargé: Marie
// ... attend chargerPosts ...
// Posts chargés: 5
// ... attend chargerComments ...
// Comments chargés: 12
// Tout est chargé !
0s : Début
0-2s : Chargement user...
2s : User chargé
2-3s : Chargement posts...
3s : Posts chargés
3-4s : Chargement comments...
4s : Comments chargés
Si les opérations sont indépendantes, on peut les lancer en parallèle avec Promise.all() !
async function charger() {
const user = await chargerUser();
const posts = await chargerPosts();
const comments = await chargerComments();
// Total : 6 secondes
// (2s + 2s + 2s)
}
async function charger() {
const resultats = await Promise.all([
chargerUser(),
chargerPosts(),
chargerComments()
]);
// Total : 2 secondes
// (les 3 en même temps !)
}
async function charger() {
const [user, posts, comments] = await Promise.all([
chargerUser(),
chargerPosts(),
chargerComments()
]);
console.log(user, posts, comments);
}
// Toutes les requêtes se font EN MÊME TEMPS !
✅ Gain de temps énorme quand les opérations sont indépendantes !
Faire des requêtes HTTP
Fetch est une fonction JavaScript pour faire des requêtes HTTP (appeler des APIs, charger des données...).
Fetch, c'est comme commander un produit sur Amazon :
fetch() retourne une Promise, donc on peut utiliser .then() ou async/await !
// Charger une liste d'utilisateurs
fetch("https://jsonplaceholder.typicode.com/users")
.then(response => {
console.log("Réponse reçue:", response);
return response.json(); // Convertit en JSON
})
.then(users => {
console.log("Utilisateurs:", users);
console.log("Premier user:", users[0].name);
})
.catch(error => {
console.log("Erreur:", error);
});
// Affichage :
// Réponse reçue: Response {status: 200, ok: true, ...}
// Utilisateurs: [{id: 1, name: "Leanne Graham", ...}, ...]
// Premier user: Leanne Graham
async function chargerUtilisateurs() {
try {
const response = await fetch("https://jsonplaceholder.typicode.com/users");
const users = await response.json();
console.log("Utilisateurs:", users);
console.log("Premier:", users[0].name);
} catch (error) {
console.log("Erreur:", error);
}
}
chargerUtilisateurs();
Fetch retourne un objet Response qui contient des informations sur la réponse HTTP.
const response = await fetch("https://api.example.com/data");
console.log(response);
| Propriété | Description | Exemple |
|---|---|---|
| status | Code HTTP | 200, 404, 500... |
| ok | Succès ? (200-299) | true / false |
| json() | Convertit body en JSON | await response.json() |
| text() | Convertit body en texte | await response.text() |
⚠️ Important : Fetch ne rejette PAS la Promise pour les erreurs HTTP (404, 500...). Il faut vérifier manuellement !
const response = await fetch(url);
const data = await response.json();
// Si erreur 404 → essaie quand même
// de parser le JSON → ERREUR !
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Erreur ${response.status}`);
}
const data = await response.json();
async function fetchJSON(url) {
try {
const response = await fetch(url);
// Vérifier le statut
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
// Convertir en JSON
const data = await response.json();
return data;
} catch (error) {
console.error("Erreur Fetch:", error.message);
throw error; // Repropager l'erreur
}
}
// Utilisation
const users = await fetchJSON("https://api.example.com/users");
Par défaut, fetch fait un GET. Pour envoyer des données (POST, PUT...), on passe un objet de configuration.
async function creerUtilisateur(user) {
const response = await fetch("https://api.example.com/users", {
method: "POST", // Méthode HTTP
headers: {
"Content-Type": "application/json" // Type de contenu
},
body: JSON.stringify(user) // Données (en JSON)
});
if (!response.ok) {
throw new Error(`Erreur ${response.status}`);
}
const data = await response.json();
return data;
}
// Utilisation
const nouveauUser = {
nom: "Marie",
email: "marie@example.com"
};
const resultat = await creerUtilisateur(nouveauUser);
console.log("Créé:", resultat);
async function modifierUtilisateur(id, user) {
const response = await fetch(`https://api.example.com/users/${id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(user)
});
return await response.json();
}
async function supprimerUtilisateur(id) {
const response = await fetch(`https://api.example.com/users/${id}`, {
method: "DELETE"
});
if (!response.ok) {
throw new Error(`Erreur ${response.status}`);
}
// Certaines APIs retournent du contenu, d'autres non
if (response.status === 204) {
return null; // Pas de contenu
}
return await response.json();
}
// Utilisation
await supprimerUtilisateur(123);
console.log("Utilisateur supprimé");
| Méthode | Action | Exemple |
|---|---|---|
| GET | Récupérer des données | Lister les users |
| POST | Créer une ressource | Créer un user |
| PUT | Modifier une ressource (complète) | Modifier un user |
| PATCH | Modifier partiellement | Changer juste l'email |
| DELETE | Supprimer une ressource | Supprimer un user |
Tout mettre en pratique
<button id="charger-btn">Charger les utilisateurs</button>
<div id="loader" style="display: none;">Chargement...</div>
<ul id="liste-users"></ul>
const bouton = document.querySelector("#charger-btn");
const loader = document.querySelector("#loader");
const liste = document.querySelector("#liste-users");
async function chargerUtilisateurs() {
try {
// Afficher le loader
loader.style.display = "block";
bouton.disabled = true;
// Requête
const response = await fetch("https://jsonplaceholder.typicode.com/users");
if (!response.ok) {
throw new Error(`Erreur HTTP ${response.status}`);
}
const users = await response.json();
// Afficher les utilisateurs
liste.innerHTML = "";
users.forEach(user => {
const li = document.createElement("li");
li.textContent = `${user.name} - ${user.email}`;
liste.appendChild(li);
});
} catch (error) {
alert("Erreur: " + error.message);
} finally {
// Cacher le loader (toujours exécuté)
loader.style.display = "none";
bouton.disabled = false;
}
}
bouton.addEventListener("click", chargerUtilisateurs);
<form id="user-form">
<input type="text" id="nom" placeholder="Nom" required>
<input type="email" id="email" placeholder="Email" required>
<button type="submit">Créer</button>
</form>
<div id="message"></div>
const form = document.querySelector("#user-form");
const messageDiv = document.querySelector("#message");
async function creerUtilisateur(event) {
event.preventDefault();
// Récupérer les données du formulaire
const nom = document.querySelector("#nom").value;
const email = document.querySelector("#email").value;
const userData = {
name: nom,
email: email
};
try {
messageDiv.textContent = "Création en cours...";
const response = await fetch("https://jsonplaceholder.typicode.com/users", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(userData)
});
if (!response.ok) {
throw new Error("Erreur lors de la création");
}
const newUser = await response.json();
messageDiv.textContent = `✅ Utilisateur créé : ${newUser.name}`;
messageDiv.style.color = "green";
// Réinitialiser le formulaire
form.reset();
} catch (error) {
messageDiv.textContent = `❌ Erreur : ${error.message}`;
messageDiv.style.color = "red";
}
}
form.addEventListener("submit", creerUtilisateur);
Recherche qui attend que l'utilisateur ait fini de taper avant de lancer la requête.
<input type="text" id="recherche" placeholder="Rechercher un user...">
<ul id="resultats"></ul>
const input = document.querySelector("#recherche");
const resultats = document.querySelector("#resultats");
let timeoutId;
async function rechercher(terme) {
if (terme.length < 2) {
resultats.innerHTML = "";
return;
}
try {
const response = await fetch("https://jsonplaceholder.typicode.com/users");
const users = await response.json();
// Filtrer les résultats
const filtres = users.filter(user =>
user.name.toLowerCase().includes(terme.toLowerCase())
);
// Afficher
resultats.innerHTML = "";
if (filtres.length === 0) {
resultats.innerHTML = "Aucun résultat ";
} else {
filtres.forEach(user => {
const li = document.createElement("li");
li.textContent = user.name;
resultats.appendChild(li);
});
}
} catch (error) {
resultats.innerHTML = `Erreur: ${error.message} `;
}
}
input.addEventListener("input", (event) => {
// Annuler le timeout précédent
clearTimeout(timeoutId);
// Lancer un nouveau timeout (300ms)
timeoutId = setTimeout(() => {
rechercher(event.target.value);
}, 300);
});
| Concept | Syntaxe | Usage |
|---|---|---|
| Callback | setTimeout(callback, 1000) |
Fonction appelée plus tard |
| Promise | new Promise((resolve, reject) => ...) |
Résultat futur d'une opération |
| .then() | promise.then(data => ...) |
Gérer le succès |
| .catch() | promise.catch(err => ...) |
Gérer l'erreur |
| .finally() | promise.finally(() => ...) |
Toujours exécuté |
| async | async function() {...} |
Fonction qui retourne une Promise |
| await | const data = await promise |
Attendre une Promise |
| fetch | fetch(url) |
Requête HTTP |
| Promise.all | await Promise.all([p1, p2]) |
Attendre plusieurs Promises |
L'asynchrone est partout en JavaScript moderne ! APIs, bases de données, fichiers... Maîtriser ces concepts est essentiel.
Le MÊME code écrit de 3 façons différentes :
chargerUser(123, (user, err) => {
if (err) {
console.log(err);
return;
}
chargerPosts(user.id, (posts, err) => {
if (err) {
console.log(err);
return;
}
console.log(posts);
});
});
chargerUser(123)
.then(user => chargerPosts(user.id))
.then(posts => console.log(posts))
.catch(err => console.log(err));
async function afficherPosts() {
try {
const user = await chargerUser(123);
const posts = await chargerPosts(user.id);
console.log(posts);
} catch (err) {
console.log(err);
}
}
afficherPosts();
✅ Utilisez async/await ! C'est la syntaxe moderne, lisible et recommandée.
Vous maîtrisez maintenant :
⏱️ Le concept d'asynchrone
📞 Les callbacks
🤝 Les Promises (then, catch, finally)
✨ Async/Await (syntaxe moderne)
🌐 Fetch API (requêtes HTTP)
❌ La gestion des erreurs
Pratiquez avec des APIs réelles ! 💪
Formation DWWM - 2026