Pourquoi attendre ? Les callbacks
Comprendre pourquoi JavaScript ne reste jamais planté devant une tâche lente, et ce que cela change dans l'ordre d'exécution.
Objectifs de la séance
- distinguer synchrone et asynchrone ;
- comprendre pourquoi un programme JavaScript n'attend pas une tâche lente ;
- prédire l'ordre d'exécution avec
setTimeout; - écrire une fonction qui fournit son résultat plus tard grâce à un callback ;
- voir pourquoi les callbacks imbriqués deviennent vite illisibles.
Notions théoriques
Le restaurant : synchrone ou asynchrone ?
Imaginez un serveur dans un restaurant.
- Mode synchrone : il prend la commande de la table 1, va en cuisine, attend que le plat soit prêt (10 minutes), le sert, et seulement ensuite s'occupe de la table 2. Les autres clients patientent sans pouvoir rien commander.
- Mode asynchrone : il prend la commande de la table 1, la transmet à la cuisine et continue avec la table 2. Quand le plat est prêt, la cuisine le prévient et il vient le servir.
JavaScript fonctionne en mode asynchrone : il n'y a qu'un seul serveur (un seul fil d'exécution), mais il ne reste jamais planté devant la cuisine.
Pourquoi c'est vital pour une application
Certaines opérations sont lentes : une requête réseau (de 50 ms à plusieurs secondes), une lecture dans une base de données, un minuteur. Si JavaScript attendait la fin de chacune, l'écran du téléphone serait figé pendant tout ce temps : plus de défilement, plus de bouton.
Le code synchrone : ligne après ligne
Jusqu'à maintenant, tout votre code était synchrone : chaque ligne attend la fin de la précédente.
console.log("1");
console.log("2");
console.log("3");
1
2
3
Le code asynchrone : « je continue, rappelle-moi »
Avec setTimeout, vous demandez : « exécute cette fonction dans N millisecondes ». JavaScript range le callback de côté et continue le programme.
console.log("1. Début");
setTimeout(() => {
console.log("3. Une seconde plus tard");
}, 1000);
console.log("2. Fin du programme");
1. Début
2. Fin du programme
3. Une seconde plus tard
Même avec 0 milliseconde !
Un callback asynchrone passe toujours après tout le code synchrone en cours, même avec un délai de 0.
console.log("A");
setTimeout(() => console.log("B"), 0);
console.log("C");
A
C
B
Image à retenir : JavaScript termine d'abord ce qu'il est en train de faire (la « pile »). Ensuite seulement, il regarde la file d'attente des callbacks prêts à être appelés.
Un résultat qui arrive plus tard : le callback
Une fonction asynchrone ne peut pas utiliser return pour donner son résultat (il n'est pas encore là !). Elle appelle un callback quand c'est prêt.
function chargerScore(callback) {
setTimeout(() => {
callback(12); // le résultat est prêt : on rappelle
}, 500);
}
chargerScore((score) => {
console.log(`Score reçu : ${score}`);
});
console.log("En attente du score...");
En attente du score...
Score reçu : 12
Le problème : la pyramide de callbacks
Comment charger un score puis, avec ce score, charger un classement ? On imbrique les callbacks.
chargerScore((score) => {
chargerClassement(score, (classement) => {
chargerRecompense(classement, (recompense) => {
console.log(recompense);
});
});
});
Chaque étape ajoute un niveau d'indentation (c'est la « pyramide du destin », ou callback hell). La gestion d'erreur devient pénible : chaque niveau doit gérer son propre échec. Les promesses (prochaine séance) ont été inventées pour résoudre ce problème.
Test de mémorisation/compréhension
TP pour réfléchir et résoudre des problèmes
Votre mission : prévoir l'ordre d'exécution, puis écrire vos premières fonctions qui répondent « plus tard ». Travaillez dans le fichier labo-js/attendre.js.
Étape 1 — Prévoir l'ordre
Avant d'exécuter, écrivez sur papier l'ordre dans lequel s'afficheront les 4 lignes. Puis lancez le programme pour vérifier.
console.log("1");
setTimeout(() => console.log("2"), 1000);
setTimeout(() => console.log("3"), 0);
console.log("4");
L'ordre est 1, 4, 3, 2 :
1et4sont synchrones et passent en premier ;3a un délai de 0 ms ;2a un délai de 1000 ms.
Prévoir le résultat, puis comparer, est la meilleure façon de comprendre l'asynchrone. Si votre prévision est fausse, relisez la section « Même avec 0 milliseconde ».
Étape 2 — Un résultat qui arrive plus tard
Écrivez chargerScore(callback) : après 500 ms, elle appelle le callback avec la valeur 12.
La fonction qui travaille ne renvoie pas le résultat : elle le transmet au callback. Celui qui appelle décide ce qu'il en fait (l'afficher, le stocker...).
Étape 3 — Deux étapes à la suite
Chargez le score, puis calculez le pourcentage (sur 20) et affichez-le. Le second chargement ne peut démarrer qu'une fois le premier terminé : on imbrique.
Avec deux étapes, ça reste lisible. Imaginez cinq étapes, avec la gestion d'erreur à chaque niveau : c'est exactement le problème que les promesses résolvent à la prochaine séance.
📌 Une solution
Ce qu'il faut retenir
| Notion | Résumé |
|---|---|
| Synchrone | Chaque ligne attend la fin de la précédente. |
| Asynchrone | Une tâche lente est lancée, le programme continue, et un callback est appelé à la fin. |
| Un seul fil | JavaScript n'exécute qu'une chose à la fois, mais ne reste pas bloqué à attendre. |
setTimeout(f, 0) | Passe quand même après tout le code synchrone en cours. |
| Callback imbriqué | Fonctionne mais devient vite illisible : c'est la pyramide de callbacks. |
Aperçu de la prochaine séance
Les promesses remplacent la pyramide par une chaîne lisible, et centralisent la gestion des erreurs. Vous apprendrez à créer vos propres promesses
et à les utiliser avec then, catch et finally.