async/await, c'est juste des promesses déguisées
async/await n'est pas un nouveau modèle de concurrence. C'est du sucre syntaxique posé sur les promesses, qui te permet d'écrire du code qui ressemble à du séquentiel alors qu'il est asynchrone. Même mécanique, forme beaucoup plus agréable.
Voici la même tâche écrite des deux façons :
Les deux fonctions renvoient une promesse. Elles font exactement la même chose. La version async se lit de haut en bas, sans enchaînement de .then — et c'est tout l'intérêt.
async : marquer une fonction comme retournant une promesse
Placez async devant une function, une fonction fléchée ou une méthode, et deux choses se produisent :
- La fonction renvoie toujours une promesse. Ce que vous passez à
returndevient la valeur résolue. - Vous pouvez utiliser
awaità l'intérieur.
Remarquez que result n'est pas la chaîne de caractères — c'est une promesse qui se résout avec la chaîne. Même si greet ne contient aucun await ni aucun traitement asynchrone, le mot-clé async enveloppe quand même la valeur de retour dans une promesse. Si la fonction lève une exception, la promesse est rejetée.
await : mettre en pause jusqu'à ce qu'une promesse soit résolue
À l'intérieur d'une fonction async, await unePromesse suspend l'exécution de la fonction jusqu'à ce que la promesse soit résolue, puis vous renvoie la valeur obtenue. Si la promesse est rejetée, await lève une exception.
Observez bien l'ordre d'affichage. "compte à rebours démarré" s'affiche avant "2" — parce qu'await met en pause uniquement la fonction async, pas le reste du programme. La boucle d'événements continue de tourner ; countdown reprend simplement plus tard, quand chaque promesse wait est résolue.
Tu peux utiliser await sur tout ce qui ressemble à une promesse. await 42 est parfaitement valide — les valeurs qui ne sont pas des promesses sont enveloppées dans un Promise.resolve(42) et résolues immédiatement.
Gérer les erreurs avec try/catch
Avec les promesses classiques, on enchaîne avec .catch(). Avec async/await, une promesse rejetée devient une exception levée que tu peux attraper de manière tout à fait classique :
Un seul try/catch couvre tous les await qu'il contient. Les erreurs réseau, les erreurs de parsing JSON et vos propres throw atterrissent tous dans le même catch. C'est un vrai progrès par rapport aux chaînes .then/.catch imbriquées.
Un point à surveiller : fetch ne rejette que sur les erreurs réseau, pas sur les codes HTTP 4xx/5xx. C'est à vous de vérifier res.ok et de déclencher une exception — un pattern que vous croiserez en permanence dans du vrai code.
Évitez await dans une boucle quand ce n'est pas nécessaire
C'est le piège le plus courant avec async/await. Un await séquentiel à l'intérieur d'une boucle force chaque itération à attendre la précédente :
sequential prend environ 900 ms. parallel tourne en ~300 ms. La règle à retenir : si les tâches ne dépendent pas du résultat les unes des autres, lancez-les toutes en même temps, puis faites un await Promise.all. N'attendez une à une avec await que lorsque l'appel suivant a réellement besoin du résultat précédent.
Pour les collections, l'idiome, c'est Promise.all(items.map(async (x) => ...)). Un simple for...of avec un await à l'intérieur s'exécute de manière séquentielle — parfois c'est ce que l'on veut (pour limiter le débit, conserver l'ordre), mais le plus souvent non.
Mélanger async/await et promesses classiques
Pas besoin de choisir son camp. Les fonctions async renvoient des promesses, et await fonctionne sur n'importe quelle promesse — on peut donc tout combiner sans souci :
Les deux styles sont interchangeables. Privilégie await quand le code se lit mieux de haut en bas, et .then pour un cas ponctuel ou quand tu es en dehors d'un contexte async.
await au niveau supérieur (dans les modules ES)
Historiquement, il fallait envelopper await dans une fonction async, car son usage était interdit au plus haut niveau d'un script. Ça a changé : dans un module ES (un fichier .mjs ou une balise <script type="module">), on peut désormais utiliser await directement au niveau supérieur :
// dans un module ES
const res = await fetch("https://jsonplaceholder.typicode.com/users/1");
const user = await res.json();
console.log(user.name);
await au niveau racine retarde la finalisation du module jusqu'à ce que la promesse attendue soit résolue — et tout module qui l'importe attend aussi. C'est pratique pour charger de la config ou gérer des imports dynamiques, mais à utiliser avec parcimonie : un await top-level lent bloque tous ceux qui importent le module.
Dans un fichier CommonJS ou un script classique inline, ça plante toujours avec une SyntaxError. La parade habituelle, c'est la fonction async auto-invoquée :
Les pièges classiques à éviter
Petit tour d'horizon des erreurs qu'on voit tout le temps :
- Oublier le
async. Utiliserawaitdans une fonction normale, c'est une erreur de syntaxe. La solution : ajouterasync— ou appeler la fonction asynchrone avec.then. - Oublier d'
awaitle résultat.const data = getJSON(url);te renvoie une promesse, pas la donnée elle-même. Si tu l'utilises comme si c'était la valeur, tu verras[object Promise]s'afficher dans ta sortie. - Les rejets non gérés. Une fonction async lancée sans surveillance (
doWork();) va avaler les erreurs en silence, sauf si tu ajoutes un.catchou si tu l'awaitdans untry/catch. forEachavec des callbacks async.array.forEach(async (x) => await something(x))n'attend absolument rien —forEachignore les promesses renvoyées. Utilise plutôtfor...ofavecawait, ou bienPromise.all(array.map(...)).
Lance le script : "fini ?" s'affiche avant n'importe quel "terminé", puisque broken rend la main sans rien attendre. fixed, lui, patiente jusqu'au bout, et c'est "fini !" qui sort en dernier.
Quand utiliser async/await en JavaScript
Par défaut, pars sur async/await dès que ton code enchaîne plusieurs étapes asynchrones, ou qu'il a besoin d'une gestion d'erreurs en try/catch. Garde les promesses brutes pour les one-liners sans intérêt, pour du code de librairie qui renvoie une promesse sans avoir rien à attendre lui-même, ou quand tu as vraiment besoin de combinateurs comme Promise.race ou d'un .finally() dans une chaîne.
Bien utilisé, async/await donne à ton code asynchrone des airs de recette de cuisine : fais ceci, puis ceci, puis cela. La boucle d'événements tourne toujours en coulisses — simplement, tu n'as plus besoin de raisonner en callbacks.
La suite : l'API fetch
Dans la plupart des exemples ici, fetch jouait le rôle de "truc asynchrone quelconque". Ça vaut le coup de s'y pencher sérieusement : comment fonctionnent les requêtes et les réponses, comment manipuler du JSON, comment définir des headers, et pourquoi fetch ne rejette pas sur une erreur HTTP. C'est l'objet de la page suivante.
Questions fréquentes
À quoi sert async/await en JavaScript ?
async/await est une syntaxe construite au-dessus des promesses qui permet d'écrire du code asynchrone comme s'il était synchrone. async indique qu'une fonction renvoie une promesse, et await met en pause l'exécution de cette fonction jusqu'à ce que la promesse soit résolue, puis récupère sa valeur. En coulisses, ce sont toujours des promesses — simplement beaucoup plus lisibles.
Peut-on utiliser await en dehors d'une fonction async ?
Au niveau racine d'un module ES, oui : c'est ce qu'on appelle le top-level await. Dans une fonction classique ou dans un script CommonJS, non — utiliser await en dehors d'une fonction async déclenche une erreur de syntaxe. La parade habituelle : encapsuler le code dans une fonction async qu'on appelle aussitôt, ou basculer le fichier en module ES.
Comment gérer les erreurs avec async/await ?
Il suffit d'entourer les appels await d'un bloc try/catch. Toute promesse rejetée que vous attendez se transforme en exception que le catch peut intercepter. Pour les tâches en arrière-plan que vous n'attendez pas, pensez à accrocher un .catch() sur la promesse retournée — sinon vous vous retrouverez avec des unhandled rejections.
Est-ce que await bloque tout le programme ?
Non. await met uniquement en pause la fonction async courante. L'event loop, lui, continue de tourner : les timers se déclenchent, les autres tâches asynchrones avancent, l'interface reste réactive. Le code appelant récupère immédiatement une promesse en attente et poursuit son exécution.