Comprendre le fonctionnement interne de async/await en JavaScript

async et await sont des ajouts syntactiques au langage JavaScript visant à simplifier la gestion des opérations asynchrones. Ils s'appuiant sur le mécanisme des Promises pour offrir une manière plus lisible et structurée d'écrire du code non bloquant.

Principes Fondamentaux

1. La fonciton async

Déclarer une fonction avec le mot-clé async indique qu'elle retournera toujours une Promise. La valeur de retour explicite de la fonction sera encapsulée dans une Promise résolue. Si une erreur est levée, elle sera capturée et résolu avec une Promise rejetée.

async function saluer() {
 return "Bonjour";
}

saluer().then(message => console.log(message)); // Affiche: Bonjour

Ce code est fonctionnellement équivalent à :

function saluer() {
 return Promise.resolve("Bonjour");
}

2. L'expression await

Le mot-clé await ne peut être utilisé qu'à l'intérieur d'une fonction async. Il met en pause l'exécution de la fonction async jusqu'à ce que la Promise sur laquelle il opère soit résolue ou rejetée. Une fois la Promise traitée, l'exécution de la fonction async reprend, et la valeur résolue (ou l'erreur rejetée) est disponible.

async function recupererDonneesUtilisateur() {
 try {
   const reponse = await fetch('https://api.example.com/users/1');
   if (!reponse.ok) {
     throw new Error(`Erreur HTTP: ${reponse.status}`);
   }
   const donnees = await reponse.json();
   console.log("Données utilisateur:", donnees);
   return donnees;
 } catch (erreur) {
   console.error("Impossible de récupérer les données:", erreur);
 }
}

Transformation en Générateurs et Promesses

Les compilateurs modernes comme Babel transforment le code utilisant async/await en une combinaison de générateurs (Generator) et de Promises pour assurer la compatibilité avec les environnements JavaScript plus anciens.

Code Original

async function lireFichier(chemin) {
 const contenu = await fs.readFile(chemin, 'utf8');
 console.log(contenu);
 return contenu.length;
}

Code Transfomré (schéma simplifié)

Le code est réorganisé en une fonction qui retourne une Promise. À l'intérieur, un générateur est utilisé pour gérer le flux d'exécution étape par étape, en utilisant yield pour suspendre l'exécution à chaque opération asynchrone (représentée par une Promise).

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
var _fs = require("fs");
var fs = _interopRequireDefault(_fs);

function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
 try {
   var info = gen[key](arg);
   var value = info.value;
   if (value && typeof value.then === "function") {
     value.then(resolve, reject);
   } else {
     resolve(value);
   }
 } catch (error) {
   reject(error);
 }
}

function _asyncToGenerator(fn) {
 return function() {
   var self = this,
     args = arguments;
   return new Promise(function(resolve, reject) {
     var gen = fn.apply(self, args);
     function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); }
     function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); }
     _next(undefined);
   });
 };
}

function lireFichier(_x) {
 return _lireFichier.apply(this, arguments);
}

function _lireFichier() {
 _lireFichier = _asyncToGenerator( /*#__PURE__*/ regeneratorRuntime.mark(function _callee(chemin) {
   var contenu;
   return regeneratorRuntime.wrap(function _callee$(_context) {
     while (1) {
       switch (_context.prev = _context.next) {
         case 0:
           _context.next = 2;
           return fs.readFile(chemin, 'utf8');

         case 2:
           contenu = _context.sent;
           console.log(contenu);
           return _context.abrupt("return", contenu.length);

         case 4:
         case "end":
           return _context.stop();
       }
     }
   }, _callee, this);
 }));
 return _lireFichier.apply(this, arguments);
}

La fonction regeneratorRuntime.mark et regeneratorRuntime.wrap proviennent de la bibliothèque regenerator-runtime, qui est essentielle pour la compilation des générateurs et des fonctions async dans les environnements plus anciens.

Gestion des Erreurs

Les erreurs dans les fonctions async, qu'elles proviennent de await sur une Promise rejetée ou d'une exception levée directement dans la fonction, peuvent être gérées à l'aide des blocs try...catch standards.

async function effectuerOperation() {
 try {
   const resultat = await operationAsynchroneQuiPeutEchouer();
   console.log("Succès:", resultat);
 } catch (erreur) {
   console.error("Échec de l'opération:", erreur);
   // Logique de gestion d'erreur
 }
}

Bonnes Pratiques

  • Utilisez await pour séquencer des opérations asynchrones lorsque l'une dépend du résultat de la précédente.
  • Pour des opérations asynchrones indépendantes qui peuvent s'exécuter en parallèle, privilégiez Promise.all() afin d'optimiser les performances.
  • Assurez-vous que toutes les erreurs potentielles lors de l'utilisation de await sont capturées avec try...catch.
  • La transpilation avec Babel et @babel/preset-env est recommandée pour supporter une large gamme de navigateurs et d'environnements Node.js.

Étiquettes: async/await JavaScript Promise générateurs Transpilation

Publié le 13 juin à 01h26