Aller au contenu
NestJS OpenPartie 1, section b
Changer de langue : lire en anglais

b · L'asynchrone : callbacks, promesses et async/await

  • Environ 45 minutes
  • 4 exercices

Lire un fichier, interroger une base de données, appeler une autre API : ces opérations prennent du temps. Un serveur backend ne peut pas rester bloqué à attendre — il doit pouvoir traiter d’autres requêtes pendant ce temps-là. C’est pour ça que ces opérations sont asynchrones.

La première façon de gérer l’asynchrone en JavaScript : passer une fonction à exécuter une fois le résultat prêt.

chargerJoueur(id, (joueur) => {
console.log(joueur.pseudo);
});

Ça fonctionne, mais s’enchaîner plusieurs appels asynchrones les uns dans les autres devient vite illisible — on appelle ça le callback hell. Le JavaScript moderne a une meilleure solution.

Une promesse (Promise) représente une valeur qui sera disponible plus tard. Elle a trois états : en attente, tenue (fulfilled, avec une valeur) ou rompue (rejected, avec une erreur).

Réécrivons chargerJoueur pour qu’elle renvoie une promesse, au lieu d’attendre un callback :

function chargerJoueur(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (id) resolve({ id, pseudo: 'Clémence' });
else reject(new Error('id manquant'));
}, 100);
});
}
chargerJoueur(1)
.then((joueur) => console.log(joueur.pseudo))
.catch((erreur) => console.error(erreur.message));

.then() s’exécute si la promesse est tenue, .catch() si elle est rompue.

async/await est une syntaxe qui rend le code asynchrone aussi lisible que du code synchrone. Une fonction async renvoie toujours une promesse, et await attend qu’une promesse soit tenue avant de continuer.

async function afficherJoueur(id) {
try {
const joueur = await chargerJoueur(id);
console.log(joueur.pseudo);
} catch (erreur) {
console.error(erreur.message);
}
}

await ne peut être utilisé qu’à l’intérieur d’une fonction async. Les erreurs se gèrent avec un simple try/catch, comme du code synchrone.

Si tu attends plusieurs promesses indépendantes, ne les enchaîne pas avec plusieurs await : elles attendraient l’une après l’autre. Promise.all les lance en même temps.

async function chargerVillage(idsJoueurs) {
const joueurs = await Promise.all(idsJoueurs.map((id) => chargerJoueur(id)));
return joueurs;
}

Exercices 1.4 à 1.7

Écris une fonction attendre(ms) qui renvoie une promesse tenue après ms millisecondes (utilise setTimeout et resolve, sans valeur). Utilise-la avec .then() pour afficher un message après une seconde.

Réécris l’exercice précédent avec async/await. Puis écris une fonction qui attend deux délais différents (par exemple 500 ms et 800 ms) en parallèle avec Promise.all, et affiche le temps total écoulé — il doit être proche de 800 ms, pas de 1300 ms.

Écris une fonction à callback chargerJoueur(id, callback), sur le modèle de l’exemple en début de section : elle simule un délai avec setTimeout, puis appelle callback(joueur). Écris ensuite chargerJoueurPromesse(id), qui fait la même chose mais renvoie une Promise au lieu de prendre un callback — c’est ce qu’on appelle promisifier. Utilise-la avec await.

Écris une fonction async chargerJoueurAvecRepli(id) qui appelle chargerJoueurPromesse de l’exercice précédent. Si la promesse est rompue (par exemple parce que id est manquant), intercepte l’erreur avec try/catch et renvoie { pseudo: 'Invité' } à la place, plutôt que de laisser planter le programme.