Aller au contenu principal

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​


Pourquoi une application ne doit-elle pas attendre « sur place » la réponse d'un serveur ?


Que s'affiche-t-il dans cet ordre ? console.log("A"); setTimeout(() => console.log("B"), 0); console.log("C");


Pourquoi une fonction asynchrone ne peut-elle pas simplement utiliser return pour donner son résultat ?


Qu'appelle-t-on « pyramide de callbacks » (callback hell) ?


JavaScript est-il capable d'exécuter plusieurs lignes de code exactement en même temps ?



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 :

  1. 1 et 4 sont synchrones et passent en premier ;
  2. 3 a un délai de 0 ms ;
  3. 2 a un délai de 1000 ms.

Bonne pratique - Prévoir avant d'exécuter

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.


Bonne pratique - Le callback reçoit le résultat

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.


Bonne pratique - Repérer la pyramide

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​

NotionRésumé
SynchroneChaque ligne attend la fin de la précédente.
AsynchroneUne tâche lente est lancée, le programme continue, et un callback est appelé à la fin.
Un seul filJavaScript 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.