Kodokon kodokon.com

Asynchrone en profondeur : promesses, async/await, gestion d'erreurs

Exploitez la boucle d'événements, parallélisez correctement vos promesses et fiabilisez la gestion d'erreurs asynchrone.

11 min · 3 questions

Ouvrir cette leçon dans Kodokon

Le modèle d'exécution repose sur une file de macrotâches (setTimeout, E/S) et une file de microtâches (.then, await, queueMicrotask). Après chaque tâche, le moteur vide intégralement la file des microtâches avant de reprendre. Conséquence directe : une promesse résolue passe toujours avant un setTimeout(fn, 0), et une chaîne de microtâches trop longue peut geler le rendu aussi sûrement qu'une boucle synchrone.

JAVASCRIPT
console.log("début");
setTimeout(() => console.log("macrotâche"), 0);
Promise.resolve().then(() => console.log("microtâche"));
console.log("fin");
// début, fin, microtâche, macrotâche
Les microtâches passent avant toute macrotâche en attente.

async/await est du sucre au-dessus des promesses, mais il induit un piège de performance : chaque await dans une boucle sérialise les opérations. Si les requêtes sont indépendantes, lancez-les toutes puis attendez le lot avec Promise.all. Pour trois requêtes de 100 ms, la version séquentielle coûte 300 ms, la version parallèle 100 ms.

JAVASCRIPT
const fetchUser = (id) => new Promise((res) => {
  setTimeout(() => res({ id }), 100);
});
async function loadSequential(ids) {
  const users = [];
  for (const id of ids) {
    users.push(await fetchUser(id)); // ~100 ms chacun
  }
  return users;
}
async function loadParallel(ids) {
  return Promise.all(ids.map(fetchUser)); // ~100 ms total
}
Même résultat, latence divisée par le nombre d'appels.

Choisissez le bon combinateur selon le contrat d'échec : Promise.all rejette dès le premier échec (tout-ou-rien), allSettled attend tout le monde et décrit chaque issue, race prend le premier règlement (succès ou échec, utile pour un timeout), any prend le premier succès et ne rejette que si tout échoue. Pour un tableau de bord qui doit afficher les résultats partiels, allSettled est presque toujours le bon choix.

JAVASCRIPT
const tasks = [
  Promise.resolve("profil chargé"),
  Promise.reject(new Error("réseau indisponible")),
];
const results = await Promise.allSettled(tasks);
for (const r of results) {
  if (r.status === "fulfilled") {
    console.log("succès :", r.value);
  } else {
    console.error("échec :", r.reason.message);
  }
}
Chaque résultat est décrit, aucun échec ne masque les autres.

Quiz de validation

Vérifiez que vous avez bien retenu les points clés de cette leçon.

  1. Dans quel ordre s'affichent console.log synchrone, callback de Promise.resolve().then et callback de setTimeout(fn, 0) ?
    • synchrone, setTimeout, then
    • synchrone, then, setTimeout
    • then, synchrone, setTimeout
  2. Vous lancez dix requêtes indépendantes. Quelle approche minimise la latence totale ?
    • Un await par requête dans une boucle for...of
    • Créer toutes les promesses puis await Promise.all
    • Enchaîner les .then les uns après les autres
    • Utiliser setTimeout pour espacer les appels
  3. Pourquoi try { doAsync(); } catch (e) {} sans await n'attrape-t-il pas le rejet de doAsync ?
    • Parce que catch ne fonctionne pas dans les fonctions async
    • Parce que le rejet survient après la sortie du bloc try, la promesse n'étant pas attendue
    • Parce que les promesses ne peuvent être attrapées qu'avec .catch