</> HTML5Advent
ENFRESDEITPT

// js · Web Platform Advent #3

async/await en JavaScript : guide pratique

Comment fonctionnent async et await, leur lien avec les promesses, la gestion d'erreurs avec try/catch, await dans les boucles et le piège série-vs-parallèle — avec des exemples exécutables.

Les mains d'un développeur tapant au clavier, avec du code source coloré à l'écran

Les mots-clés async et await sont une syntaxe au-dessus des promesses. Ils permettent d'écrire du code asynchrone qui se lit de haut en bas comme du code synchrone, tout en restant non bloquant en coulisse. Si vous connaissez les promesses, vous connaissez déjà async/await — il supprime simplement le cérémonial des .then().

Les bases

Marquez une fonction async et vous pouvez utiliser await à l'intérieur. await met cette fonction en pause jusqu'à ce que la promesse soit réglée, puis vous donne la valeur résolue :

async function getUser(id) {
  const res = await fetch('/api/user/' + id);
  const user = await res.json();
  return user.name;
}

getUser(42).then((name) => console.log(name));

Deux choses à retenir : une fonction async renvoie toujours une promesse (ce que vous renvoyez est emballé dedans), et await ne fonctionne qu'à l'intérieur d'une fonction async — sauf au niveau racine d'un module ES, où le top-level await est autorisé.

async/await vs promesses

C'est la même mécanique. Cette chaîne de promesses…

fetch('/api/user')
  .then((res) => res.json())
  .then((user) => console.log(user.name));

…est exactement équivalente à cette version async, que la plupart trouvent plus simple à lire et à déboguer :

const res = await fetch('/api/user');
const user = await res.json();
console.log(user.name);

On peut les mélanger librement — await n'importe quelle promesse, y compris le résultat de Promise.all().

Gestion d'erreurs avec try/catch

Avec async/await, on attrape les promesses rompues avec un try/catch ordinaire, la même construction que pour les erreurs synchrones :

async function loadUser(id) {
  try {
    const res = await fetch('/api/user/' + id);
    if (!res.ok) throw new Error('HTTP ' + res.status);
    return await res.json();
  } catch (err) {
    console.error('Impossible de charger l’utilisateur :', err.message);
    return null;
  } finally {
    console.log('loadUser terminé');
  }
}

Un await rompu lève une exception à cette ligne, donc le bloc catch s'exécute. Le bloc finally s'exécute qu'il y ait eu une erreur ou non.

Une autoroute à plusieurs voies avec voitures et camions roulant côte à côte en parallèle
Des tâches indépendantes peuvent s'exécuter côte à côte comme des véhicules sur des voies parallèles — les attendre une par une impose au contraire une file à la queue leu leu.

Le grand piège : await en série vs en parallèle

L'erreur async/await la plus courante est d'attendre des tâches indépendantes l'une après l'autre. Ceci s'exécute en série — la deuxième requête ne démarre qu'une fois la première terminée :

// Lent : temps total = a + b + c
const a = await fetchA();
const b = await fetchB();
const c = await fetchC();

Si les tâches ne dépendent pas l'une de l'autre, lancez-les toutes d'abord, puis attendez ensemble avec Promise.all. Elles s'exécutent alors en parallèle :

// Rapide : temps total ≈ la plus lente
const [a, b, c] = await Promise.all([fetchA(), fetchB(), fetchC()]);

await dans les boucles

Une simple boucle for avec await traite les éléments un par un, ce qui est correct quand chaque étape dépend de la précédente ou qu'il faut éviter de surcharger un serveur :

for (const id of ids) {
  const item = await fetchItem(id);   // séquentiel — l’un après l’autre
  console.log(item);
}

Pour les traiter en parallèle, transformez en tableau de promesses et await Promise.all. Attention : forEach n'attend pas les callbacks async — utilisez map + Promise.all à la place :

const items = await Promise.all(ids.map((id) => fetchItem(id)));

Aide-mémoire

ObjectifPattern
Attendre une promesseconst x = await p;
Gérer les erreurstry { await ... } catch (e) { ... }
Tâches indépendantes en parallèleawait Promise.all([...])
Boucle séquentiellefor...of avec await
Boucle concurrentemapPromise.all

async/await ne remplace pas les promesses — il les rend lisibles. Gardez Promise.all à portée pour le parallélisme, utilisez try/catch pour les erreurs, et méfiez-vous de l'await en série accidentel : vous aurez alors le JavaScript asynchrone bien en main.