Comprendre la boucle d’événement, les rappels, les promesses et les Async/Await en JavaScript

L’auteur a sélectionné le Fonds de Secours COVID-19 pour recevoir un don dans le cadre du programme Écrire pour les Dons.

Introduction

Aux premiers jours d’Internet, les sites web étaient souvent constitués de données statiques dans une page HTML. Mais maintenant que les applications web sont devenues plus interactives et dynamiques, il est devenu de plus en plus nécessaire d’effectuer des opérations intensives telles que faire des requêtes réseau externes pour récupérer des données API. Pour gérer ces opérations en JavaScript, un développeur doit utiliser des techniques de programmation asynchrone.

Étant donné que JavaScript est un langage de programmation monofilaire avec un modèle d’exécution synchrone qui traite une opération après l’autre, il ne peut traiter qu’une instruction à la fois. Cependant, une action comme la demande de données à partir d’une API peut prendre un temps indéterminé, en fonction de la taille des données demandées, de la vitesse de la connexion réseau et d’autres facteurs. Si les appels API étaient effectués de manière synchrone, le navigateur ne pourrait pas gérer d’entrée utilisateur, comme le défilement ou le clic sur un bouton, tant que cette opération n’est pas terminée. Cela est connu sous le nom de bloquage.

Pour éviter les comportements bloquants, l’environnement du navigateur dispose de nombreuses API Web auxquelles JavaScript peut accéder de manière asynchrone, ce qui signifie qu’elles peuvent s’exécuter en parallèle avec d’autres opérations au lieu de manière séquentielle. Cela est utile car cela permet à l’utilisateur de continuer à utiliser normalement le navigateur pendant que les opérations asynchrones sont en cours de traitement.

En tant que développeur JavaScript, vous devez savoir comment travailler avec les API Web asynchrones et gérer la réponse ou l’erreur de ces opérations. Dans cet article, vous apprendrez sur la boucle d’événement, la méthode originale de gestion du comportement asynchrone à travers les rappels, l’ajout ECMAScript 2015 des promesses, et la pratique moderne d’utilisation de async/await.

Remarque : Cet article est axé sur le JavaScript côté client dans l’environnement du navigateur. Les mêmes concepts sont généralement vrais dans l’environnement Node.js, cependant Node.js utilise ses propres APIs en C++ contrairement aux APIs Web du navigateur. Pour plus d’informations sur la programmation asynchrone en Node.js, consultez Comment Écrire du Code Asynchrone en Node.js.

La Boucle d’Événement

Cette section expliquera comment JavaScript gère le code asynchrone avec la boucle des événements. Elle passera d’abord par une démonstration de la boucle des événements en action, puis expliquera les deux éléments de la boucle des événements : la pile et la file d’attente.

Le code JavaScript qui n’utilise pas d’API Web asynchrones s’exécutera de manière synchrone, un à la fois, séquentiellement. Cela est démontré par ce code d’exemple qui appelle trois fonctions qui impriment chacune un nombre dans la console :

// Définir trois fonctions d'exemple
function first() {
  console.log(1)
}

function second() {
  console.log(2)
}

function third() {
  console.log(3)
}

Dans ce code, vous définissez trois fonctions qui impriment des nombres avec console.log().

Ensuite, écrivez les appels aux fonctions :

// Exécuter les fonctions
first()
second()
third()

La sortie sera basée sur l’ordre des appels aux fonctions : first(), second(), puis third() :

Output
1 2 3

Lorsqu’une API Web asynchrone est utilisée, les règles deviennent plus compliquées. Une API intégrée que vous pouvez tester avec cela est setTimeout, qui définit une minuterie et effectue une action après un certain laps de temps. setTimeout doit être asynchrone, sinon tout le navigateur resterait figé pendant l’attente, ce qui entraînerait une mauvaise expérience utilisateur.

Ajoutez setTimeout à la fonction second pour simuler une requête asynchrone :

// Définir trois fonctions d'exemple, mais l'une d'elles contient du code asynchrone
function first() {
  console.log(1)
}

function second() {
  setTimeout(() => {
    console.log(2)
  }, 0)
}

function third() {
  console.log(3)
}

setTimeout prend deux arguments : la fonction qu’elle exécutera de manière asynchrone et le temps d’attente avant d’appeler cette fonction. Dans ce code, vous avez enveloppé console.log dans une fonction anonyme et l’avez passée à setTimeout, puis vous avez défini la fonction pour s’exécuter après 0 millisecondes.

Maintenant, appelez les fonctions, comme vous l’avez fait précédemment :

// Exécuter les fonctions
first()
second()
third()

Vous pourriez vous attendre, avec un setTimeout réglé sur 0 millisecondes, que l’exécution de ces trois fonctions donnerait toujours les nombres imprimés dans l’ordre séquentiel. Mais parce qu’il est asynchrone, la fonction avec le délai s’imprimera en dernier :

Output
1 3 2

Que vous régliez le délai sur zéro seconde ou cinq minutes ne fera aucune différence : le console.log appelé par du code asynchrone s’exécutera après les fonctions synchrones de niveau supérieur. Cela se produit car l’environnement hôte JavaScript, dans ce cas le navigateur, utilise un concept appelé la boucle événementielle pour gérer la concurrence ou les événements parallèles. Étant donné que JavaScript ne peut exécuter qu’une seule instruction à la fois, il a besoin que la boucle événementielle soit informée de quand exécuter quelle instruction spécifique. La boucle événementielle gère cela avec les concepts de pile et de file d’attente.

Pile

La pile, ou pile d’appels, contient l’état de la fonction qui s’exécute actuellement. Si vous n’êtes pas familier avec le concept de pile, vous pouvez l’imaginer comme un tableau avec des propriétés « dernier entré, premier sorti » (LIFO), ce qui signifie que vous ne pouvez ajouter ou supprimer des éléments qu’à la fin de la pile. JavaScript exécutera le cadre actuel (ou l’appel de fonction dans un environnement spécifique) dans la pile, puis le retirera et passera au suivant.

Pour l’exemple ne contenant que du code synchrone, le navigateur gère l’exécution dans l’ordre suivant:

  • Ajouter first() à la pile, exécuter first() qui enregistre 1 dans la console, retirer first() de la pile.
  • Ajouter second() à la pile, exécuter second() qui enregistre 2 dans la console, retirer second() de la pile.
  • Ajouter third() à la pile, exécuter third() qui enregistre 3 dans la console, retirer third() de la pile.

Le deuxième exemple avec setTimeout ressemble à ceci:

  • Ajouter first() à la pile, exécuter first() qui enregistre 1 dans la console, retirer first() de la pile.
  • Ajouter second() à la pile, exécuter second().
    • Ajouter setTimeout() à la pile, exécuter le Web API setTimeout() qui lance un minuteur et ajoute la fonction anonyme à la file d’attente, retirer setTimeout() de la pile.
  • Retirez second() de la pile.
  • Ajoutez third() à la pile, exécutez third() qui enregistre 3 dans la console, puis retirez third() de la pile.
  • La boucle des événements vérifie la file d’attente pour tout message en attente et trouve la fonction anonyme de setTimeout(), ajoute la fonction à la pile qui enregistre 2 dans la console, puis la retire de la pile.

L’utilisation de setTimeout, un Web API asynchrone, introduit le concept de la file d’attente, que ce tutoriel couvrira ensuite.

File d’attente

La file d’attente, également appelée file de messages ou file de tâches, est une zone d’attente pour les fonctions. Chaque fois que la pile d’appels est vide, la boucle des événements vérifie la file d’attente pour tout message en attente, en commençant par le plus ancien message. Une fois qu’elle en trouve un, elle l’ajoute à la pile, qui exécutera la fonction dans le message.

Dans l’exemple de setTimeout, la fonction anonyme s’exécute immédiatement après le reste de l’exécution de niveau supérieur, car le minuteur a été réglé sur 0 secondes. Il est important de se rappeler que le minuteur ne signifie pas que le code s’exécutera exactement dans 0 secondes ou à l’heure spécifiée, mais qu’il ajoutera la fonction anonyme à la file d’attente dans ce laps de temps. Ce système de file d’attente existe car si le minuteur ajoutait la fonction anonyme directement à la pile lorsque le minuteur se termine, cela interromprait la fonction en cours d’exécution, ce qui pourrait avoir des effets non voulus et imprévisibles.

Remarque : Il existe également une autre file d’attente appelée la file d’attente de tâches ou file d’attente de microtâches qui gère les promesses. Les microtâches comme les promesses sont traitées avec une priorité plus élevée que les macrotâches telles que setTimeout.

Maintenant, vous savez comment la boucle d’événement utilise la pile et la file d’attente pour gérer l’ordre d’exécution du code. La prochaine tâche consiste à comprendre comment contrôler l’ordre d’exécution dans votre code. Pour ce faire, vous apprendrez d’abord la manière originale de garantir que le code asynchrone est traité correctement par la boucle d’événement : les fonctions de rappel.

Fonctions de rappel

Dans l’exemple de setTimeout, la fonction avec le délai s’est exécutée après tout dans le contexte d’exécution principal de niveau supérieur. Mais si vous vouliez vous assurer qu’une des fonctions, comme la fonction third, s’exécute après le délai, alors vous devriez utiliser des méthodes de codage asynchrones. Le délai ici peut représenter un appel d’API asynchrone contenant des données. Vous voulez travailler avec les données de l’appel d’API, mais vous devez vous assurer que les données sont renvoyées en premier.

La solution originale à ce problème consiste à utiliser les fonctions de rappel. Les fonctions de rappel n’ont pas de syntaxe spéciale ; ce sont simplement des fonctions qui ont été passées en tant qu’argument à une autre fonction. La fonction qui prend une autre fonction en argument est appelée une fonction d’ordre supérieur. Selon cette définition, n’importe quelle fonction peut devenir une fonction de rappel si elle est passée en tant qu’argument. Les rappels ne sont pas asynchrones par nature, mais peuvent être utilisés à des fins asynchrones.

Voici un exemple de code syntaxique d’une fonction d’ordre supérieur et d’un rappel :

// Une fonction
function fn() {
  console.log('Just a function')
}

// Une fonction qui prend une autre fonction en argument
function higherOrderFunction(callback) {
  // Lorsque vous appelez une fonction qui est passée en tant qu'argument, elle est appelée un rappel
  callback()
}

// Passage d'une fonction
higherOrderFunction(fn)

Dans ce code, vous définissez une fonction fn, vous définissez une fonction higherOrderFunction qui prend une fonction callback en argument, et vous passez fn en tant que rappel à higherOrderFunction.

Exécuter ce code donnera ce qui suit :

Output
Just a function

Revenons aux premier, deuxième et troisième fonctions avec setTimeout. Voici ce que vous avez jusqu’à présent :

function first() {
  console.log(1)
}

function second() {
  setTimeout(() => {
    console.log(2)
  }, 0)
}

function third() {
  console.log(3)
}

La tâche consiste à obtenir que la fonction troisième retarde toujours son exécution jusqu’à ce que l’action asynchrone dans la fonction deuxième soit terminée. C’est là que les rappels interviennent. Au lieu d’exécuter premier, deuxième et troisième au niveau supérieur de l’exécution, vous passerez la fonction troisième en argument à deuxième. La fonction deuxième exécutera le rappel après que l’action asynchrone soit terminée.

Voici les trois fonctions avec un rappel appliqué :

// Définir trois fonctions
function first() {
  console.log(1)
}

function second(callback) {
  setTimeout(() => {
    console.log(2)

    // Exécuter la fonction de rappel
    callback()
  }, 0)
}

function third() {
  console.log(3)
}

Maintenant, exécutez premier et deuxième, puis passez troisième en argument à deuxième :

first()
second(third)

Après avoir exécuté ce bloc de code, vous recevrez la sortie suivante :

Output
1 2 3

En premier lieu, 1 sera imprimé, et après que le minuteur soit terminé (dans ce cas, zéro seconde, mais vous pouvez le modifier pour n’importe quelle durée), il imprimera 2 puis 3. En passant une fonction en tant que rappel, vous avez réussi à retarder l’exécution de la fonction jusqu’à ce que l’action asynchrone de l’API Web (setTimeout) soit terminée.

L’élément clé ici est que les fonctions de rappel ne sont pas asynchrones—setTimeout est l’API Web asynchrone chargée de gérer les tâches asynchrones. Le rappel vous permet simplement d’être informé lorsque une tâche asynchrone est terminée et gère le succès ou l’échec de la tâche.

Maintenant que vous avez appris à utiliser les rappels pour gérer les tâches asynchrones, la section suivante explique les problèmes de l’emboîtement de trop de rappels et la création d’une « pyramide de la mort ».

Rappels Emboîtés et Pyramide de la Mort

Les fonctions de rappel sont un moyen efficace de garantir l’exécution différée d’une fonction jusqu’à ce qu’une autre se termine et renvoie des données. Cependant, en raison de la nature emboîtée des rappels, le code peut devenir confus si vous avez beaucoup de requêtes asynchrones consécutives qui dépendent les unes des autres. C’était une grande frustration pour les développeurs JavaScript au début, et en conséquence, le code contenant des rappels emboîtés est souvent appelé la « pyramide de la mort » ou « l’enfer des rappels ».

Voici une démonstration de rappels emboîtés :

function pyramidOfDoom() {
  setTimeout(() => {
    console.log(1)
    setTimeout(() => {
      console.log(2)
      setTimeout(() => {
        console.log(3)
      }, 500)
    }, 2000)
  }, 1000)
}

Dans ce code, chaque nouveau setTimeout est emboîté à l’intérieur d’une fonction d’ordre supérieur, créant une forme de pyramide de rappels de plus en plus profonds. L’exécution de ce code donnerait ce qui suit :

Output
1 2 3

Dans la pratique, avec du code asynchrone réel, cela peut devenir beaucoup plus compliqué. Vous devrez très probablement gérer les erreurs dans le code asynchrone, puis transmettre certaines données de chaque réponse à la demande suivante. Faire cela avec des rappels rendra votre code difficile à lire et à maintenir.

Voici un exemple exécutable d’un « pyramid of doom » plus réaliste avec lequel vous pouvez jouer:

// Fonction asynchrone d'exemple
function asynchronousRequest(args, callback) {
  // Lancer une erreur si aucun argument n'est passé
  if (!args) {
    return callback(new Error('Whoa! Something went wrong.'))
  } else {
    return setTimeout(
      // Ajouter simplement un nombre aléatoire pour donner l'impression que la fonction asynchrone construite
      // a retourné des données différentes
      () => callback(null, {body: args + ' ' + Math.floor(Math.random() * 10)}),
      500,
    )
  }
}

// Requêtes asynchrones imbriquées
function callbackHell() {
  asynchronousRequest('First', function first(error, response) {
    if (error) {
      console.log(error)
      return
    }
    console.log(response.body)
    asynchronousRequest('Second', function second(error, response) {
      if (error) {
        console.log(error)
        return
      }
      console.log(response.body)
      asynchronousRequest(null, function third(error, response) {
        if (error) {
          console.log(error)
          return
        }
        console.log(response.body)
      })
    })
  })
}

// Exécuter 
callbackHell()

Dans ce code, vous devez faire en sorte que chaque fonction prenne en compte une éventuelle response et une éventuelle error, rendant la fonction callbackHell visuellement confuse.

Exécuter ce code vous donnera le résultat suivant:

Output
First 9 Second 3 Error: Whoa! Something went wrong. at asynchronousRequest (<anonymous>:4:21) at second (<anonymous>:29:7) at <anonymous>:9:13

Cette manière de gérer le code asynchrone est difficile à suivre. Par conséquent, le concept de promesses a été introduit dans ES6. C’est le sujet de la prochaine section.

Les Promesses

A promise represents the completion of an asynchronous function. It is an object that might return a value in the future. It accomplishes the same basic goal as a callback function, but with many additional features and a more readable syntax. As a JavaScript developer, you will likely spend more time consuming promises than creating them, as it is usually asynchronous Web APIs that return a promise for the developer to consume. This tutorial will show you how to do both.

Création d’une promesse

Vous pouvez initialiser une promesse avec la syntaxe new Promise, et vous devez l’initialiser avec une fonction. La fonction passée à une promesse a des paramètres resolve et reject. Les fonctions resolve et reject gèrent respectivement le succès et l’échec d’une opération.

Écrivez la ligne suivante pour déclarer une promesse :

// Initialiser une promesse
const promise = new Promise((resolve, reject) => {})

Si vous inspectez la promesse initialisée dans cet état avec la console de votre navigateur web, vous constaterez qu’elle a un statut pending et une valeur undefined:

Output
__proto__: Promise [[PromiseStatus]]: "pending" [[PromiseValue]]: undefined

Jusqu’à présent, rien n’a été configuré pour la promesse, elle restera donc dans un état pending indéfiniment. La première chose que vous pouvez faire pour tester une promesse est de remplir la promesse en la résolvant avec une valeur :

const promise = new Promise((resolve, reject) => {
  resolve('We did it!')
})

Maintenant, en inspectant la promesse, vous constaterez qu’elle a un statut de fulfilled, et une valeur définie sur la valeur que vous avez passée à resolve:

Output
__proto__: Promise [[PromiseStatus]]: "fulfilled" [[PromiseValue]]: "We did it!"

Comme indiqué au début de cette section, une promesse est un objet qui peut renvoyer une valeur. Après avoir été remplie avec succès, la valeur passe de undefined à être remplie de données.

A promise can have three possible states: pending, fulfilled, and rejected.

  • En attente – État initial avant d’être résolue ou rejetée
  • Rempli – Opération réussie, la promesse a été résolue
  • Rejeté – Opération échouée, la promesse a été rejetée

Une fois remplie ou rejetée, une promesse est réglée.

Maintenant que vous avez une idée de la manière dont les promesses sont créées, voyons comment un développeur peut les consommer.

Consommer une promesse

La promesse dans la dernière section a été remplie avec une valeur, mais vous voulez également pouvoir accéder à cette valeur. Les promesses ont une méthode appelée then qui s’exécutera après qu’une promesse atteint resolve dans le code. then renverra la valeur de la promesse en tant que paramètre.

Voici comment vous retourneriez et enregistreriez la valeur de la promesse exemple:

promise.then((response) => {
  console.log(response)
})

La promesse que vous avez créée avait une [[PromiseValue]] de Nous l'avons fait !. Cette valeur est ce qui sera transmis dans la fonction anonyme en tant que réponse:

Output
We did it!

Jusqu’à présent, l’exemple que vous avez créé n’impliquait pas d’API Web asynchrone, il expliquait uniquement comment créer, résoudre et consommer une promesse JavaScript native. En utilisant setTimeout, vous pouvez tester une requête asynchrone.

Le code suivant simule les données renvoyées par une requête asynchrone sous forme de promesse:

const promise = new Promise((resolve, reject) => {
  setTimeout(() => resolve('Resolving an asynchronous request!'), 2000)
})

// Enregistrer le résultat
promise.then((response) => {
  console.log(response)
})

Utiliser la syntaxe then garantit que la réponse sera enregistrée uniquement lorsque l’opération setTimeout est terminée après 2000 millisecondes. Tout cela est fait sans imbriquer de rappels.

Maintenant, après deux secondes, elle résoudra la valeur de la promesse et elle sera enregistrée dans then:

Output
Resolving an asynchronous request!

Les promesses peuvent également être enchaînées pour transmettre des données à plus d’une opération asynchrone. Si une valeur est renvoyée dans then, un autre then peut être ajouté qui se réalisera avec la valeur de retour du then précédent:

// Enchaîner une promesse
promise
  .then((firstResponse) => {
    // Renvoyer une nouvelle valeur pour le prochain then
    return firstResponse + ' And chaining!'
  })
  .then((secondResponse) => {
    console.log(secondResponse)
  })

La réponse accomplie dans le deuxième then enregistrera la valeur de retour:

Output
Resolving an asynchronous request! And chaining!

Puisque then peut être enchaîné, cela permet à la consommation des promesses de sembler plus synchrone que les rappels, car ils n’ont pas besoin d’être imbriqués. Cela permettra un code plus lisible qui peut être maintenu et vérifié plus facilement.

Gestion des erreurs

Jusqu’à présent, vous n’avez géré qu’une promesse avec un resolve réussi, ce qui met la promesse dans un état accompli. Mais souvent, avec une requête asynchrone, vous devez également gérer une erreur – si l’API est hors service, ou si une demande malformée ou non autorisée est envoyée. Une promesse devrait être capable de gérer les deux cas. Dans cette section, vous allez créer une fonction pour tester à la fois le succès et l’échec de la création et de la consommation d’une promesse.

Cette fonction getUsers passera un indicateur à une promesse et renverra la promesse :

function getUsers(onSuccess) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      // Gérer le resolve et le reject dans l'API asynchrone
    }, 1000)
  })
}

Configurez le code de sorte que si onSuccess est true, le délai d’attente se terminera avec des données. Si false, la fonction rejettera avec une erreur :

function getUsers(onSuccess) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      // Gérer le resolve et le reject dans l'API asynchrone
      if (onSuccess) {
        resolve([
          {id: 1, name: 'Jerry'},
          {id: 2, name: 'Elaine'},
          {id: 3, name: 'George'},
        ])
      } else {
        reject('Failed to fetch data!')
      }
    }, 1000)
  })
}

Pour le résultat réussi, vous retournez des objets JavaScript qui représentent des données d’utilisateur d’exemple.

Pour gérer l’erreur, vous utiliserez la méthode d’instance catch. Cela vous donnera un rappel d’échec avec l’erreur en paramètre.

Exécutez la commande getUser avec onSuccess défini sur false, en utilisant la méthode then pour le cas de succès et la méthode catch pour l’erreur :

// Exécuter la fonction getUsers avec le drapeau false pour déclencher une erreur
getUsers(false)
  .then((response) => {
    console.log(response)
  })
  .catch((error) => {
    console.error(error)
  })

Depuis que l’erreur a été déclenchée, le then sera ignoré et le catch gérera l’erreur :

Output
Failed to fetch data!

Si vous inversez le drapeau et que vous utilisez resolve à la place, le catch sera ignoré et les données seront retournées à la place :

// Exécuter la fonction getUsers avec le drapeau true pour résoudre avec succès
getUsers(true)
  .then((response) => {
    console.log(response)
  })
  .catch((error) => {
    console.error(error)
  })

Cela renverra les données utilisateur :

Output
(3) [{…}, {…}, {…}] 0: {id: 1, name: "Jerry"} 1: {id: 2, name: "Elaine"} 3: {id: 3, name: "George"}

Pour référence, voici un tableau avec les méthodes de gestionnaire sur les objets Promise :

Method Description
then() Handles a resolve. Returns a promise, and calls onFulfilled function asynchronously
catch() Handles a reject. Returns a promise, and calls onRejected function asynchronously
finally() Called when a promise is settled. Returns a promise, and calls onFinally function asynchronously

Les promesses peuvent être déroutantes, tant pour les nouveaux développeurs que pour les programmeurs expérimentés qui n’ont jamais travaillé dans un environnement asynchrone auparavant. Cependant, comme mentionné, il est beaucoup plus courant de consommer des promesses que d’en créer. Habituellement, une API Web du navigateur ou une bibliothèque tierce fournira la promesse, et vous n’avez qu’à la consommer.

Dans la section finale sur les promesses, ce tutoriel citera un cas d’utilisation courant d’une API Web qui renvoie des promesses : l’API Fetch.

Utilisation de l’API Fetch avec des promesses

L’une des API Web les plus utiles et couramment utilisées qui renvoie une promesse est l’API Fetch, qui vous permet de faire une demande de ressource asynchrone sur un réseau. fetch est un processus en deux parties et nécessite donc le chaînage de then. Cet exemple montre comment accéder à l’API GitHub pour récupérer les données d’un utilisateur, tout en traitant également toute erreur potentielle :

// Récupérer un utilisateur depuis l'API GitHub
fetch('https://api.github.com/users/octocat')
  .then((response) => {
    return response.json()
  })
  .then((data) => {
    console.log(data)
  })
  .catch((error) => {
    console.error(error)
  })

La requête fetch est envoyée à l’URL https://api.github.com/users/octocat, qui attend de manière asynchrone une réponse. Le premier then passe la réponse à une fonction anonyme qui formate la réponse en données JSON, puis passe le JSON à un deuxième then qui enregistre les données dans la console. L’instruction catch enregistre toute erreur dans la console.

L’exécution de ce code produira ce qui suit :

Output
login: "octocat", id: 583231, avatar_url: "https://avatars3.githubusercontent.com/u/583231?v=4" blog: "https://github.blog" company: "@github" followers: 3203 ...

Voici les données demandées depuis https://api.github.com/users/octocat, rendues au format JSON.

Cette partie du tutoriel a montré que les promesses intègrent beaucoup d’améliorations pour traiter le code asynchrone. Mais, bien que l’utilisation de then pour gérer les actions asynchrones soit plus facile à suivre que la pyramide de rappels, certains développeurs préfèrent toujours un format synchrone pour écrire du code asynchrone. Pour répondre à ce besoin, ECMAScript 2016 (ES7) a introduit les fonctions async et le mot-clé await pour faciliter le travail avec les promesses.

Fonctions asynchrones avec async/await

Une fonction asynchrone vous permet de gérer du code asynchrone d’une manière qui semble synchrone. Les fonctions async utilisent toujours des promesses sous-jacentes, mais ont une syntaxe JavaScript plus traditionnelle. Dans cette section, vous allez essayer des exemples de cette syntaxe.

Vous pouvez créer une fonction async en ajoutant le mot-clé async avant une fonction :

// Créer une fonction asynchrone
async function getUser() {
  return {}
}

Bien que cette fonction ne gère pas encore quelque chose de manière asynchrone, elle se comporte différemment d’une fonction traditionnelle. Si vous exécutez la fonction, vous constaterez qu’elle renvoie une promesse avec un [[PromiseStatus]] et un [[PromiseValue]] au lieu d’une valeur de retour.

Essayez ceci en journalisant un appel à la fonction getUser :

console.log(getUser())

Cela donnera le résultat suivant :

Output
__proto__: Promise [[PromiseStatus]]: "fulfilled" [[PromiseValue]]: Object

Cela signifie que vous pouvez gérer une fonction async avec then de la même manière que vous pourriez gérer une promesse. Essayez ceci avec le code suivant :

getUser().then((response) => console.log(response))

Cet appel à getUser passe la valeur de retour à une fonction anonyme qui journalise la valeur dans la console.

Vous recevrez le résultat suivant lorsque vous exécutez ce programme :

Output
{}

Une fonction async peut gérer une promesse appelée à l’intérieur d’elle-même en utilisant l’opérateur await. await peut être utilisé à l’intérieur d’une fonction async et attendra qu’une promesse se règle avant d’exécuter le code désigné.

Avec cette connaissance, vous pouvez réécrire la requête Fetch de la dernière section en utilisant async/await comme suit :

// Gérer fetch avec async/await
async function getUser() {
  const response = await fetch('https://api.github.com/users/octocat')
  const data = await response.json()

  console.log(data)
}

// Exécuter la fonction async
getUser()

Les opérateurs await ici garantissent que les données ne sont pas enregistrées avant que la requête ne les ait peuplées avec des données.

Maintenant, les données finales peuvent être traitées à l’intérieur de la fonction getUser, sans avoir besoin d’utiliser then. Voici la sortie de l’enregistrement des data:

Output
login: "octocat", id: 583231, avatar_url: "https://avatars3.githubusercontent.com/u/583231?v=4" blog: "https://github.blog" company: "@github" followers: 3203 ...

Remarque: Dans de nombreux environnements, async est nécessaire pour utiliser await — cependant, certaines nouvelles versions de navigateurs et de Node permettent d’utiliser await au niveau supérieur, ce qui vous permet de contourner la création d’une fonction asynchrone pour envelopper le await.

Enfin, puisque vous gérez la promesse accomplie à l’intérieur de la fonction asynchrone, vous pouvez également gérer l’erreur à l’intérieur de la fonction. Au lieu d’utiliser la méthode catch avec then, vous utiliserez le motif try/catch pour gérer l’exception.

Ajoutez le code suivant en surbrillance :

// Gérer le succès et les erreurs avec async/await
async function getUser() {
  try {
    // Gérer le succès avec try
    const response = await fetch('https://api.github.com/users/octocat')
    const data = await response.json()

    console.log(data)
  } catch (error) {
    // Gérer l'erreur avec catch
    console.error(error)
  }
}

Le programme passera maintenant au bloc catch s’il reçoit une erreur et enregistrera cette erreur dans la console.

Le code asynchrone JavaScript moderne est le plus souvent géré avec la syntaxe async/await, mais il est important d’avoir une connaissance pratique de la façon dont les promesses fonctionnent, surtout parce que les promesses sont capables de fonctionnalités supplémentaires qui ne peuvent pas être gérées avec async/await, comme combiner des promesses avec Promise.all().

Remarque: async/await peut être reproduit en utilisant des générateurs combinés avec des promesses pour ajouter plus de flexibilité à votre code. Pour en savoir plus, consultez notre Tutoriel sur la compréhension des générateurs en JavaScript.

Conclusion

Parce que les API Web fournissent souvent des données de manière asynchrone, apprendre à gérer le résultat des actions asynchrones est une partie essentielle du métier de développeur JavaScript. Dans cet article, vous avez appris comment l’environnement hôte utilise la boucle d’événements pour gérer l’ordre d’exécution du code avec la pile et la file d’attente. Vous avez également essayé des exemples de trois façons de gérer le succès ou l’échec d’un événement asynchrone, avec des rappels, des promesses et la syntaxe async/await. Enfin, vous avez utilisé l’API Web Fetch pour gérer les actions asynchrones.

Pour plus d’informations sur la façon dont le navigateur gère les événements parallèles, consultez le document Modèle de concurrence et la boucle d’événements sur le réseau des développeurs Mozilla. Si vous souhaitez en savoir plus sur JavaScript, revenez à notre série Comment Coder en JavaScript.

Source:
https://www.digitalocean.com/community/tutorials/understanding-the-event-loop-callbacks-promises-and-async-await-in-javascript