L’auteur a sélectionné le Fonds pour l’Internet ouvert / la Liberté d’expression pour recevoir un don dans le cadre du programme Écrire pour les dons.
Introduction
Pour de nombreux programmes en JavaScript, le code est exécuté au fur et à mesure que le développeur l’écrit, ligne par ligne. Cela s’appelle l’exécution synchrone, car les lignes sont exécutées les unes après les autres, dans l’ordre où elles ont été écrites. Cependant, chaque instruction que vous donnez à l’ordinateur n’a pas besoin d’être exécutée immédiatement. Par exemple, si vous envoyez une requête réseau, le processus exécutant votre code devra attendre que les données reviennent avant de pouvoir les traiter. Dans ce cas, du temps serait gaspillé s’il n’exécutait pas d’autres instructions pendant l’attente de la complétion de la requête réseau. Pour résoudre ce problème, les développeurs utilisent la programmation asynchrone, dans laquelle les lignes de code sont exécutées dans un ordre différent de celui dans lequel elles ont été écrites. Avec la programmation asynchrone, nous pouvons exécuter d’autres instructions pendant que nous attendons que des activités longues comme les requêtes réseau se terminent.
Le code JavaScript est exécuté sur un seul thread au sein d’un processus informatique. Son code est traité de manière synchrone sur ce thread, avec seulement une instruction exécutée à la fois. Par conséquent, si nous devions effectuer une tâche longue sur ce thread, tout le reste du code est bloqué jusqu’à ce que la tâche soit terminée. En exploitant les fonctionnalités de programmation asynchrone de JavaScript, nous pouvons décharger les tâches longues vers un thread secondaire pour éviter ce problème. Lorsque la tâche est terminée, le code nécessaire pour traiter les données de la tâche est remis sur le thread principal unique.
Dans ce tutoriel, vous apprendrez comment JavaScript gère les tâches asynchrones avec l’aide de la Boucle d’événement, qui est une construction JavaScript qui complète une nouvelle tâche tout en attendant une autre. Vous créerez ensuite un programme qui utilise la programmation asynchrone pour demander une liste de films à partir d’une API Studio Ghibli et enregistrer les données dans un fichier CSV. Le code asynchrone sera écrit de trois manières : callbacks, promises et avec les mots-clés async
/await
.
Remarque : À la date de rédaction de cet article, la programmation asynchrone n’est plus réalisée uniquement à l’aide de callbacks, mais apprendre cette méthode obsolète peut fournir un excellent contexte pour comprendre pourquoi la communauté JavaScript utilise désormais les promises. Les mots-clés async
/await
nous permettent d’utiliser les promises de manière moins verbeuse et sont donc la méthode standard pour effectuer la programmation asynchrone en JavaScript au moment de la rédaction de cet article.
Prérequis
- Node.js installé sur votre machine de développement. Ce tutoriel utilise la version 10.17.0. Pour l’installer sur macOS ou Ubuntu 18.04, suivez les étapes dans la section Comment installer Node.js et créer un environnement de développement local sur macOS ou la section Installation à l’aide d’un PPA de Comment installer Node.js sur Ubuntu 18.04.
- Vous devrez également être familier avec l’installation de packages dans votre projet. Mettez-vous à niveau en lisant notre guide sur Comment utiliser les modules Node.js avec npm et package.json.
- Il est important que vous soyez à l’aise pour créer et exécuter des fonctions en JavaScript avant d’apprendre à les utiliser de manière asynchrone. Si vous avez besoin d’une introduction ou d’un rappel, vous pouvez lire notre guide sur Comment définir des fonctions en JavaScript
La Boucle des Événements
Commençons par étudier le fonctionnement interne de l’exécution des fonctions JavaScript. Comprendre comment cela fonctionne vous permettra d’écrire du code asynchrone de manière plus délibérée et vous aidera à déboguer le code à l’avenir.
Lorsque l’interpréteur JavaScript exécute le code, chaque fonction appelée est ajoutée à la pile d’appels JavaScript. La pile d’appels est une pile — une structure de données de type liste où les éléments ne peuvent être ajoutés qu’au sommet et retirés du sommet. Les piles suivent le principe « dernier entré, premier sorti » ou LIFO. Si vous ajoutez deux éléments à la pile, l’élément le plus récemment ajouté est retiré en premier.
Illustrons cela avec un exemple utilisant la pile d’appels. Si JavaScript rencontre une fonction functionA()
appelée, elle est ajoutée à la pile d’appels. Si cette fonction functionA()
appelle une autre fonction functionB()
, alors functionB()
est ajoutée au sommet de la pile d’appels. Lorsque JavaScript termine l’exécution d’une fonction, elle est retirée de la pile d’appels. Par conséquent, JavaScript exécutera d’abord functionB()
, la retirera de la pile lorsqu’elle sera terminée, puis terminera l’exécution de functionA()
et la retirera de la pile d’appels. C’est pourquoi les fonctions internes sont toujours exécutées avant leurs fonctions externes.
Lorsque JavaScript rencontre une opération asynchrone, comme l’écriture dans un fichier, il l’ajoute à une table dans sa mémoire. Cette table stocke l’opération, la condition pour qu’elle soit terminée et la fonction à appeler lorsque celle-ci est terminée. Au fur et à mesure que l’opération se termine, JavaScript ajoute la fonction associée à la file d’attente des messages. Une file d’attente est une autre structure de données de type liste où les éléments ne peuvent être ajoutés qu’en bas mais retirés en haut. Dans la file d’attente des messages, si deux opérations asynchrones ou plus sont prêtes pour que leurs fonctions soient exécutées, l’opération asynchrone qui s’est terminée en premier aura sa fonction marquée pour être exécutée en premier.
Les fonctions dans la file d’attente des messages attendent d’être ajoutées à la pile d’appels. La boucle d’événements est un processus perpétuel qui vérifie si la pile d’appels est vide. Si c’est le cas, alors le premier élément dans la file d’attente des messages est déplacé vers la pile d’appels. JavaScript donne la priorité aux fonctions dans la file d’attente des messages par rapport aux appels de fonction qu’il interprète dans le code. L’effet combiné de la pile d’appels, de la file d’attente des messages et de la boucle d’événements permet au code JavaScript d’être traité tout en gérant des activités asynchrones.
Maintenant que vous avez une compréhension globale de la boucle d’événements, vous savez comment le code asynchrone que vous écrivez sera exécuté. Avec cette connaissance, vous pouvez maintenant créer du code asynchrone avec trois approches différentes : les rappels (callbacks), les promesses et async
/await
.
Programmation Asynchrone avec des Rappels (Callbacks)
A callback function is one that is passed as an argument to another function, and then executed when the other function is finished. We use callbacks to ensure that code is executed only after an asynchronous operation is completed.
Pendant longtemps, les rappels étaient le mécanisme le plus courant pour écrire du code asynchrone, mais maintenant ils sont largement obsolètes car ils peuvent rendre le code difficile à lire. À cette étape, vous écrirez un exemple de code asynchrone utilisant des rappels afin de pouvoir l’utiliser comme référence pour voir l’efficacité accrue d’autres stratégies.
Il existe de nombreuses façons d’utiliser des fonctions de rappel dans une autre fonction. En général, elles ont cette structure :
Bien qu’il ne soit pas nécessaire sur le plan syntaxique en JavaScript ou Node.js d’avoir la fonction de rappel comme dernier argument de la fonction externe, c’est une pratique courante qui rend les rappels plus faciles à identifier. Il est également courant pour les développeurs JavaScript d’utiliser une fonction anonyme comme rappel. Les fonctions anonymes sont celles créées sans nom. C’est généralement beaucoup plus lisible lorsqu’une fonction est définie à la fin de la liste d’arguments.
Pour démontrer les rappels, créons un module Node.js qui écrit une liste de films Studio Ghibli dans un fichier. Tout d’abord, créez un dossier qui stockera notre fichier JavaScript et sa sortie :
Ensuite, entrez dans ce dossier :
Nous commencerons par faire une requête HTTP vers l’API Studio Ghibli, que notre fonction de rappel enregistrera les résultats. Pour ce faire, nous installerons une bibliothèque qui nous permet d’accéder aux données d’une réponse HTTP dans un rappel.
Dans votre terminal, initialisez npm pour que nous puissions avoir une référence à nos paquets plus tard :
Ensuite, installez la bibliothèque request
:
Maintenant, ouvrez un nouveau fichier appelé callbackMovies.js
dans un éditeur de texte tel que nano
:
Dans votre éditeur de texte, saisissez le code suivant. Commençons par envoyer une requête HTTP avec le module request
:
Dans la première ligne, nous chargeons le module request
qui a été installé via npm. Le module renvoie une fonction qui peut effectuer des requêtes HTTP ; nous sauvegardons ensuite cette fonction dans la constante request
.
Nous effectuons ensuite la requête HTTP en utilisant la fonction request()
. Imprimons maintenant les données de la requête HTTP dans la console en ajoutant les modifications surlignées :
Lorsque nous utilisons la fonction request()
, nous lui donnons deux paramètres :
- L’URL du site Web que nous essayons de demander
- A callback function that handles any errors or successful responses after the request is complete
Notre fonction de rappel a trois arguments : error
, response
, et body
. Lorsque la requête HTTP est terminée, les arguments reçoivent automatiquement des valeurs en fonction du résultat. Si la requête a échoué, alors error
contiendrait un objet, mais response
et body
seraient null
. Si elle a réussi à envoyer la requête, alors la réponse HTTP est stockée dans response
. Si notre réponse HTTP retourne des données (dans cet exemple nous obtenons du JSON) alors les données sont définies dans body
.
Notre fonction de rappel vérifie d’abord si nous avons reçu une erreur. Il est préférable de vérifier les erreurs dans un rappel en premier afin que l’exécution du rappel ne se poursuive pas avec des données manquantes. Dans ce cas, nous consignons l’erreur et l’exécution de la fonction. Ensuite, nous vérifions le code d’état de la réponse. Notre serveur n’est pas toujours disponible, et les API peuvent changer, ce qui rend des demandes autrefois sensées incorrectes. En vérifiant que le code d’état est 200
, ce qui signifie que la demande était « OK », nous pouvons avoir confiance que notre réponse est conforme à nos attentes.
Enfin, nous analysons le corps de la réponse pour le transformer en Array
et parcourons chaque film pour consigner son nom et son année de sortie.
Après avoir enregistré et quitté le fichier, exécutez ce script avec :
Vous obtiendrez la sortie suivante :
OutputCastle in the Sky, 1986
Grave of the Fireflies, 1988
My Neighbor Totoro, 1988
Kiki's Delivery Service, 1989
Only Yesterday, 1991
Porco Rosso, 1992
Pom Poko, 1994
Whisper of the Heart, 1995
Princess Mononoke, 1997
My Neighbors the Yamadas, 1999
Spirited Away, 2001
The Cat Returns, 2002
Howl's Moving Castle, 2004
Tales from Earthsea, 2006
Ponyo, 2008
Arrietty, 2010
From Up on Poppy Hill, 2011
The Wind Rises, 2013
The Tale of the Princess Kaguya, 2013
When Marnie Was There, 2014
Nous avons réussi à recevoir une liste de films du Studio Ghibli avec l’année de leur sortie. Maintenant, complétons ce programme en écrivant la liste des films que nous consignons actuellement dans un fichier.
Mettez à jour le fichier callbackMovies.js
dans votre éditeur de texte pour inclure le code mis en évidence suivant, qui crée un fichier CSV avec nos données de film :
Remarquant les changements mis en évidence, nous voyons que nous importons le module fs
. Ce module est standard dans toutes les installations de Node.js, et il contient une méthode writeFile()
qui peut écrire de manière asynchrone dans un fichier.
Au lieu de consigner les données dans la console, nous les ajoutons maintenant à une variable de chaîne movieList
. Ensuite, nous utilisons writeFile()
pour enregistrer le contenu de movieList
dans un nouveau fichier, callbackMovies.csv
. Enfin, nous fournissons un rappel à la fonction writeFile()
, qui a un argument : error
. Cela nous permet de gérer les cas où nous ne pouvons pas écrire dans un fichier, par exemple lorsque l’utilisateur sur lequel nous exécutons le processus node
n’a pas les permissions nécessaires.
Enregistrez le fichier et exécutez à nouveau ce programme Node.js avec :
Dans votre dossier ghibliMovies
, vous verrez callbackMovies.csv
, qui a le contenu suivant :
Castle in the Sky, 1986
Grave of the Fireflies, 1988
My Neighbor Totoro, 1988
Kiki's Delivery Service, 1989
Only Yesterday, 1991
Porco Rosso, 1992
Pom Poko, 1994
Whisper of the Heart, 1995
Princess Mononoke, 1997
My Neighbors the Yamadas, 1999
Spirited Away, 2001
The Cat Returns, 2002
Howl's Moving Castle, 2004
Tales from Earthsea, 2006
Ponyo, 2008
Arrietty, 2010
From Up on Poppy Hill, 2011
The Wind Rises, 2013
The Tale of the Princess Kaguya, 2013
When Marnie Was There, 2014
Il est important de noter que nous écrivons dans notre fichier CSV dans le rappel de la requête HTTP. Une fois que le code est dans la fonction de rappel, il n’écrira dans le fichier qu’après que la requête HTTP soit terminée. Si nous voulions communiquer avec une base de données après avoir écrit notre fichier CSV, nous créerions une autre fonction asynchrone qui serait appelée dans le rappel de writeFile()
. Plus nous avons de code asynchrone, plus il y a de fonctions de rappel imbriquées.
Imaginons que nous voulions exécuter cinq opérations asynchrones, chacune ne pouvant s’exécuter que lorsque l’autre est terminée. Si nous devions coder cela, nous aurions quelque chose comme ceci :
Lorsque les rappels imbriqués ont de nombreuses lignes de code à exécuter, ils deviennent considérablement plus complexes et illisibles. À mesure que votre projet JavaScript grandit en taille et en complexité, cet effet deviendra plus prononcé, jusqu’à devenir éventuellement ingérable. En raison de cela, les développeurs n’utilisent plus de rappels pour gérer les opérations asynchrones. Pour améliorer la syntaxe de notre code asynchrone, nous pouvons utiliser des promesses à la place.
Utilisation des Promesses pour une Programmation Asynchrone Concise
A promise is a JavaScript object that will return a value at some point in the future. Asynchronous functions can return promise objects instead of concrete values. If we get a value in the future, we say that the promise was fulfilled. If we get an error in the future, we say that the promise was rejected. Otherwise, the promise is still being worked on in a pending state.
Les promesses prennent généralement la forme suivante :
Comme le montre ce modèle, les promesses utilisent également des fonctions de rappel. Nous avons une fonction de rappel pour la méthode then()
, qui est exécutée lorsque la promesse est réalisée. Nous avons également une fonction de rappel pour la méthode catch()
pour gérer les erreurs éventuelles survenues pendant l’exécution de la promesse.
Obtenons une expérience de première main avec les promesses en réécrivant notre programme Studio Ghibli pour utiliser des promesses à la place.
Axios est un client HTTP basé sur les promesses pour JavaScript, donc allons-y et installons-le :
Maintenant, avec votre éditeur de texte préféré, créez un nouveau fichier promiseMovies.js
:
Notre programme effectuera une requête HTTP avec axios
puis utilisera une version spéciale basée sur les promesses de fs
pour enregistrer dans un nouveau fichier CSV.
Écrivez ce code dans promiseMovies.js
afin que nous puissions charger Axios et envoyer une requête HTTP à l’API de films :
Dans la première ligne, nous chargeons le module axios
, en stockant la fonction retournée dans une constante appelée axios
. Ensuite, nous utilisons la méthode axios.get()
pour envoyer une requête HTTP à l’API.
La méthode axios.get()
retourne une promesse. Chaînons cette promesse pour pouvoir afficher la liste des films Ghibli dans la console :
Décomposons ce qui se passe. Après avoir effectué une requête GET HTTP avec axios.get()
, nous utilisons la fonction then()
, qui n’est exécutée que lorsque la promesse est accomplie. Dans ce cas, nous imprimons les films à l’écran comme nous l’avons fait dans l’exemple des rappels.
Pour améliorer ce programme, ajoutez le code surligné pour écrire les données HTTP dans un fichier :
Nous importons également une fois de plus le module fs
. Remarquez comment après l’importation de fs
, nous avons .promises
. Node.js inclut une version basée sur les promesses de la bibliothèque fs
basée sur les rappels, de sorte que la compatibilité ascendante n’est pas rompue dans les projets hérités.
La première fonction then()
qui traite la requête HTTP appelle maintenant fs.writeFile()
au lieu d’imprimer dans la console. Étant donné que nous avons importé la version basée sur les promesses de fs
, notre fonction writeFile()
retourne une autre promesse. Par conséquent, nous ajoutons une autre fonction then()
pour lorsque la promesse de writeFile()
est accomplie.
A promise can return a new promise, allowing us to execute promises one after the other. This paves the way for us to perform multiple asynchronous operations. This is called promise chaining, and it is analogous to nesting callbacks. The second then()
is only called after we successfully write to the file.
Note : Dans cet exemple, nous n’avons pas vérifié le code d’état HTTP comme nous l’avons fait dans l’exemple de rappel. Par défaut, axios
ne remplit pas sa promesse s’il reçoit un code d’état indiquant une erreur. En tant que tel, nous n’avons plus besoin de le valider.
Pour compléter ce programme, enchaînez la promesse avec une fonction catch()
comme indiqué ci-dessous :
Si une promesse n’est pas réalisée dans la chaîne de promesses, JavaScript passe automatiquement à la fonction catch()
si elle a été définie. C’est pourquoi nous n’avons qu’une seule clause catch()
même si nous avons deux opérations asynchrones.
Vérifions que notre programme produit la même sortie en exécutant :
Dans votre dossier ghibliMovies
, vous verrez le fichier promiseMovies.csv
contenant :
Castle in the Sky, 1986
Grave of the Fireflies, 1988
My Neighbor Totoro, 1988
Kiki's Delivery Service, 1989
Only Yesterday, 1991
Porco Rosso, 1992
Pom Poko, 1994
Whisper of the Heart, 1995
Princess Mononoke, 1997
My Neighbors the Yamadas, 1999
Spirited Away, 2001
The Cat Returns, 2002
Howl's Moving Castle, 2004
Tales from Earthsea, 2006
Ponyo, 2008
Arrietty, 2010
From Up on Poppy Hill, 2011
The Wind Rises, 2013
The Tale of the Princess Kaguya, 2013
When Marnie Was There, 2014
Avec les promesses, nous pouvons écrire un code beaucoup plus concis que d’utiliser uniquement des rappels. La chaîne de promesses de rappels est une option plus propre que l’imbrication de rappels. Cependant, à mesure que nous effectuons plus d’appels asynchrones, notre chaîne de promesses devient plus longue et plus difficile à maintenir.
La verbeusité des rappels et des promesses provient du besoin de créer des fonctions lorsque nous avons le résultat d’une tâche asynchrone. Une meilleure expérience consisterait à attendre un résultat asynchrone et à le mettre dans une variable en dehors de la fonction. De cette façon, nous pouvons utiliser les résultats dans les variables sans avoir à créer une fonction. Nous pouvons y parvenir avec les mots-clés async
et await
.
Écrire du JavaScript avec async
/await
Les mots-clés async
/await
fournissent une syntaxe alternative lors de la manipulation de promesses. Au lieu d’avoir le résultat d’une promesse disponible dans la méthode then()
, le résultat est renvoyé comme une valeur comme dans n’importe quelle autre fonction. Nous définissons une fonction avec le mot-clé async
pour indiquer à JavaScript qu’il s’agit d’une fonction asynchrone qui renvoie une promesse. Nous utilisons le mot-clé await
pour indiquer à JavaScript de renvoyer les résultats de la promesse au lieu de renvoyer la promesse elle-même lorsqu’elle est accomplie.
En général, l’utilisation de async
/await
ressemble à ceci :
Voyons comment l’utilisation de async
/await
peut améliorer notre programme Studio Ghibli. Utilisez votre éditeur de texte pour créer et ouvrir un nouveau fichier asyncAwaitMovies.js
:
Dans votre fichier JavaScript nouvellement ouvert, commençons par importer les mêmes modules que ceux que nous avons utilisés dans notre exemple de promesse :
Les importations sont les mêmes que dans promiseMovies.js
car async
/await
utilise des promesses.
Maintenant, nous utilisons le mot-clé async
pour créer une fonction avec notre code asynchrone :
Nous créons une nouvelle fonction appelée saveMovies()
, mais nous incluons async
au début de sa définition. C’est important car nous ne pouvons utiliser le mot-clé await
que dans une fonction asynchrone.
Utilisez le mot-clé await
pour effectuer une requête HTTP qui récupère la liste des films à partir de l’API Ghibli :
Dans notre fonction saveMovies()
, nous effectuons une requête HTTP avec axios.get()
comme précédemment. Cette fois, nous ne la chaînons pas avec une fonction then()
. Au lieu de cela, nous ajoutons await
avant son appel. Lorsque JavaScript rencontre await
, il n’exécutera le reste du code de la fonction qu’après que axios.get()
ait terminé son exécution et défini la variable response
. L’autre code sauvegarde les données du film afin que nous puissions écrire dans un fichier.
Écrivons les données du film dans un fichier :
Nous utilisons également le mot-clé await
lorsque nous écrivons dans le fichier avec fs.writeFile()
.
Pour compléter cette fonction, nous devons intercepter les erreurs que nos promesses peuvent générer. Faisons cela en encapsulant notre code dans un bloc try
/catch
:
Comme les promesses peuvent échouer, nous encapsulons notre code asynchrone avec une clause try
/catch
. Cela capturera toutes les erreurs qui sont générées lorsque les opérations de requête HTTP ou d’écriture de fichier échouent.
Enfin, appelons notre fonction asynchrone saveMovies()
pour qu’elle soit exécutée lorsque nous exécutons le programme avec node
.
À première vue, cela ressemble à un bloc de code JavaScript synchrone typique. Il y a moins de fonctions qui circulent, ce qui semble un peu plus propre. Ces petits ajustements rendent le code asynchrone avec async
/await
plus facile à maintenir.
Testez cette itération de notre programme en entrant ceci dans votre terminal :
Dans votre dossier ghibliMovies
, un nouveau fichier asyncAwaitMovies.csv
sera créé avec le contenu suivant :
Castle in the Sky, 1986
Grave of the Fireflies, 1988
My Neighbor Totoro, 1988
Kiki's Delivery Service, 1989
Only Yesterday, 1991
Porco Rosso, 1992
Pom Poko, 1994
Whisper of the Heart, 1995
Princess Mononoke, 1997
My Neighbors the Yamadas, 1999
Spirited Away, 2001
The Cat Returns, 2002
Howl's Moving Castle, 2004
Tales from Earthsea, 2006
Ponyo, 2008
Arrietty, 2010
From Up on Poppy Hill, 2011
The Wind Rises, 2013
The Tale of the Princess Kaguya, 2013
When Marnie Was There, 2014
Vous avez maintenant utilisé les fonctionnalités JavaScript async
/await
pour gérer le code asynchrone.
Conclusion
Dans ce tutoriel, vous avez appris comment JavaScript gère l’exécution des fonctions et la gestion des opérations asynchrones avec la boucle des événements. Vous avez ensuite écrit des programmes qui ont créé un fichier CSV après avoir effectué une requête HTTP pour obtenir des données de film en utilisant diverses techniques de programmation asynchrone. Tout d’abord, vous avez utilisé l’approche obsolète basée sur les rappels. Ensuite, vous avez utilisé des promesses, et enfin async
/await
pour rendre la syntaxe des promesses plus concise.
Avec votre compréhension du code asynchrone avec Node.js, vous pouvez désormais développer des programmes qui bénéficient de la programmation asynchrone, comme ceux qui dépendent des appels d’API. Jetez un œil à cette liste d’API publiques. Pour les utiliser, vous devrez effectuer des requêtes HTTP asynchrones comme nous l’avons fait dans ce tutoriel. Pour approfondir vos études, essayez de construire une application qui utilise ces API pour pratiquer les techniques que vous avez apprises ici.
Source:
https://www.digitalocean.com/community/tutorials/how-to-write-asynchronous-code-in-node-js