Même les enfants comprendront: une explication simple de l'async / wait et des promesses en JavaScript

Bonjour, Habr! Je vous présente la traduction de l'article «JavaScript Async / Await and Promises: Explained like you're five years» par Jack Pordi.

Quiconque se considère comme un développeur JavaScript, à un moment donné, aurait dû rencontrer des fonctions de rappel, des promesses ou, plus récemment, la syntaxe async / wait. Si vous êtes dans le jeu depuis assez longtemps, vous avez probablement rencontré des moments où les fonctions de rappel imbriquées étaient le seul moyen de réaliser l'asynchronie en JavaScript.

Lorsque j'ai commencé à apprendre et à écrire en JavaScript, il y avait déjà un milliard de tutoriels et de tutoriels expliquant comment réaliser l'asynchronie en JavaScript. Cependant, beaucoup d'entre eux ont simplement expliqué comment convertir les fonctions de rappel en promesses ou promesses en async / wait. Pour beaucoup, c'est probablement plus que suffisant pour s'entendre avec eux et commencer à les utiliser dans leur code.

Cependant, si vous, comme moi, voulez vraiment comprendre la programmation asynchrone (et pas seulement la syntaxe JavaScript!), Alors vous serez peut-être d'accord avec moi qu'il y a une pénurie de documents expliquant la programmation asynchrone à partir de zéro.

Que signifie asynchrone?


l'image montre une personne qui pense

En règle générale, en posant cette question, vous pouvez entendre quelque chose de ce qui suit:

  • Il existe plusieurs threads qui exécutent du code en même temps.
  • Plus d'un morceau de code est exécuté à la fois.
  • C'est la simultanéité.

Dans une certaine mesure, toutes les options sont correctes. Mais au lieu de vous donner une définition technique que vous risquez d'oublier bientôt, je vais vous donner un exemple que même un enfant peut comprendre .

Exemple de vie


la photo montre des légumes et un couteau de cuisine

Imaginez que vous cuisinez une soupe aux légumes. Pour une bonne et simple analogie, supposons qu'une soupe de légumes ne se compose que d'oignons et de carottes. La recette d'une telle soupe peut être la suivante:

  1. Hacher les carottes.
  2. Hachez l'oignon.
  3. Ajoutez de l'eau dans la casserole, allumez la cuisinière et attendez qu'elle bout.
  4. Ajouter les carottes dans la poêle et laisser reposer 5 minutes.
  5. Ajouter les oignons dans la poêle et cuire encore 10 minutes.

Ces instructions sont simples et compréhensibles, mais si l'un d'entre vous, en lisant ceci, sait vraiment cuisiner, vous pouvez dire que ce n'est pas la façon la plus efficace de cuisiner. Et vous aurez raison, c'est pourquoi:

  • Les étapes 3, 4 et 5 ne vous obligent pas en tant que chef à faire quoi que ce soit, sauf pour observer le processus et suivre le temps.
  • Les étapes 1 et 2 vous obligent à faire quelque chose activement.

Par conséquent, la recette d'un cuisinier plus expérimenté peut être la suivante:

  1. Commencez à faire bouillir une casserole d'eau.
  2. En attendant que la casserole bout , commencez à couper les carottes.
  3. Au moment où vous avez fini de hacher les carottes, l'eau doit bouillir, alors ajoutez les carottes.
  4. Pendant que les carottes sont cuites dans une poêle, hachez les oignons.
  5. Ajouter les oignons et cuire encore 10 minutes.

Malgré le fait que toutes les actions sont restées les mêmes, vous avez le droit de vous attendre à ce que cette option soit beaucoup plus rapide et plus efficace. C'est exactement le principe de la programmation asynchrone: vous ne voulez jamais vous asseoir, juste attendre quelque chose, alors que vous pouvez passer votre temps sur d'autres choses utiles.

Nous savons tous qu'en programmation, attendre quelque chose se produit assez souvent - que ce soit en attente d'une réponse HTTP d'un serveur ou d'une action d'un utilisateur ou autre chose. Mais les cycles d'exécution de votre processeur sont précieux et doivent toujours être utilisés activement, faire quelque chose et ne pas s'attendre: cela vous donne une programmation asynchrone .

Passons maintenant à JavaScript, d'accord?


Donc, en suivant le même exemple de soupe aux légumes, j'écrirai quelques fonctions pour représenter les étapes de la recette décrite ci-dessus.

Tout d'abord, écrivons des fonctions synchrones qui représentent des tâches qui ne prennent pas de temps. Ce sont les bonnes vieilles fonctions JavaScript, mais notez que j'ai décrit les fonctions chopCarrots et chopOnions comme des tâches qui nécessitent un travail actif (et du temps), leur permettant de faire de longs calculs. Le code complet est disponible à la fin de l'article [1].

 function chopCarrots() { /*   ... */ console.log(" !"); } function chopOnions() { /*   ... */ console.log(" !"); } function addOnions() { console.log("   !"); } function addCarrots() { console.log("   !"); } 

Avant de passer aux fonctions asynchrones, je vais d'abord expliquer rapidement comment le système de type JavaScript gère l'asynchronie: fondamentalement, tous les résultats (y compris les erreurs) des opérations asynchrones doivent être inclus dans la ou les promesses .

Pour qu'une fonction renvoie une promesse, vous pouvez:

  • retourner explicitement la promesse, c.-à-d. return new Promise(…) ;
  • renvoie implicitement une promesse - ajoutez le async à la déclaration de fonction, c'est-à-dire async function foo() ;
  • utilisez les deux options .

Il existe un excellent article [2], qui parle de la différence entre les fonctions asynchrones et les fonctions qui renvoient une promesse. Par conséquent, dans mon article, je ne m'attarderai pas sur ce sujet, la principale chose à retenir: vous devez toujours utiliser le async dans les fonctions asynchrones.

Ainsi, nos fonctions asynchrones, représentant les étapes 3 à 5 de la préparation de soupe aux légumes, sont les suivantes:

 async function letPotKeepBoiling(time) { return; //  ,      } async function boilPot() { return; //  ,       } 

Encore une fois, j'ai supprimé les détails d'implémentation afin de ne pas être distrait par eux, mais ils sont publiés à la fin de l'article [1].

Il est important de savoir que pour attendre le résultat de la promesse, afin que plus tard vous puissiez en faire quelque chose, vous pouvez simplement utiliser le mot-clé wait:

 async function asyncFunction() { /*  ... */ } result = await asyncFunction(); 

Donc, maintenant nous avons juste besoin de tout rassembler:

 function makeSoup() { const pot = boilPot(); chopCarrots(); chopOnions(); await pot; addCarrots(); await letPotKeepBoiling(5); addOnions(); await letPotKeepBoiling(10); console.log("   !"); } makeSoup(); 

Mais attends! Ça ne marche pas! Vous verrez le SyntaxError: await is only valid in async functions . Pourquoi? Parce que si vous ne déclarez pas une fonction en utilisant le async - async , alors par défaut JavaScript la définit comme une fonction synchrone - et synchrone signifie pas d'attente! [3]. Cela signifie également que vous ne pouvez pas utiliser l' await dehors d'une fonction.

Par conséquent, nous ajoutons simplement le async à la fonction makeSoup :

 async function makeSoup() { const pot = boilPot(); chopCarrots(); chopOnions(); await pot; addCarrots(); await letPotKeepBoiling(5); addOnions(); await letPotKeepBoiling(10); console.log("   !"); } makeSoup(); 

Et le tour est joué! Notez que dans la deuxième ligne, j'appelle la fonction asynchrone boilPot sans le mot-clé wait, car nous ne voulons pas attendre que la casserole bouillonne avant de commencer à couper les carottes. Nous n'attendons que la promesse d'un pot en cinquième ligne avant de devoir mettre les carottes dans la casserole, car nous ne voulons pas le faire avant que l'eau ne bout.

Que se passe-t-il pendant les appels en await ? Eh bien, rien ... en quelque sorte ...

Dans le contexte de la fonction makeSoup vous pouvez simplement y penser comme si vous vous attendiez à ce que quelque chose se produise (ou un résultat qui sera finalement retourné).

Mais rappelez- vous : vous (comme votre processeur) ne voudrez jamais rester assis là et attendre quelque chose, pendant que vous pouvez consacrer votre temps à d'autres choses .

Par conséquent, au lieu de simplement cuisiner de la soupe, nous pourrions cuisiner autre chose en parallèle:

 makeSoup(); makePasta(); 

Pendant que nous attendons letPotKeepBoiling , nous pouvons par exemple cuisiner des pâtes.

Tu vois? La syntaxe async / attente est en fait assez facile à utiliser, si vous la comprenez, d'accord?

Et les promesses manifestes?


Eh bien, si vous insistez, je me tournerai vers l'utilisation de promesses explicites ( environ. Transl.: Par promesses explicites, l'auteur implique directement la syntaxe des promesses, et par promesses implicites la syntaxe async / wait, car elle renvoie la promesse implicitement - pas besoin d'écrire return new Promise(…) ). Gardez à l'esprit que les méthodes asynchrones / attendent sont basées sur les promesses elles-mêmes et que les deux options sont donc entièrement compatibles .

Les promesses explicites, à mon avis, se situent entre les rappels à l'ancienne et la nouvelle syntaxe sexuelle asynchrone / wait. Alternativement, vous pouvez également considérer la syntaxe sexuelle asynchrone / wait comme rien de plus que des promesses implicites. En fin de compte, la construction async / wait est venue après les promesses, qui à leur tour sont venues après les fonctions de rappel.

Utilisez notre machine à remonter le temps pour aller au enfer des rappels [4]:

 function callbackHell() { boilPot( () => { addCarrots(); letPotKeepBoiling(() => { addOnions(); letPotKeepBoiling(() => { console.log("   !"); }, 1000); }, 5000); }, 5000, chopCarrots(), chopOnions() ); } 

Je ne vais pas mentir, j'ai écrit cet exemple à la volée lorsque je travaillais sur cet article, et cela m'a pris beaucoup plus de temps que je ne voudrais l'admettre. Beaucoup d'entre vous ne savent peut-être même pas ce qui se passe ici. Mon cher ami, toutes ces fonctions de rappel ne sont-elles pas affreuses? Que ce soit une leçon de ne plus jamais utiliser les fonctions de rappel ...

Et, comme promis, le même exemple avec des promesses explicites:

 function makeSoup() { return Promise.all([ new Promise((reject, resolve) => { chopCarrots(); chopOnions(); resolve(); }), boilPot() ]) .then(() => { addCarrots(); return letPotKeepBoiling(5); }) .then(() => { addOnions(); return letPotKeepBoiling(10); }) .then(() => { console.log("   !"); }); } 

Comme vous pouvez le voir, les promesses sont toujours similaires aux fonctions de rappel.
Je n'entrerai pas dans les détails, mais surtout:

  • .then est une méthode promise qui prend son résultat et le passe à la fonction argument (essentiellement, à une fonction de rappel ...)
  • Vous ne pouvez jamais utiliser le résultat d'une promesse en dehors du contexte de .then . En substance, .then est comme un bloc asynchrone qui attend un résultat et le transmet ensuite à une fonction de rappel.
  • En plus de la méthode .then , il y a une autre méthode dans les .catch - .catch . Il est nécessaire pour gérer les erreurs dans les promesses. Mais je n'entrerai pas dans les détails, car il y a déjà un milliard d'articles et de tutoriels sur ce sujet.

Conclusion


J'espère que vous avez eu une idée des promesses et de la programmation asynchrone de cet article, ou peut-être avez-vous au moins appris un bon exemple de la vie pour l'expliquer à quelqu'un d'autre.

Alors, quelle voie utilisez-vous: promesses ou async / attente?
La réponse dépend entièrement de vous - et je dirais que les combiner n'est pas si mal, car les deux approches sont complètement compatibles l'une avec l'autre.

Cependant, je suis personnellement à 100% dans le camp asynchrone / attendent, car pour moi le code est beaucoup plus compréhensible et reflète mieux le véritable multitâche de la programmation asynchrone.



[1] : Le code source complet est disponible ici .
[2] : Article original «Fonction asynchrone vs. une fonction qui renvoie une promesse " , traduction de l'article " La différence entre une fonction asynchrone et une fonction qui renvoie une promesse . "
[3] : Vous pouvez faire valoir que JavaScript peut probablement déterminer le type asynchrone / attente par corps de fonction et vérifier récursivement, mais JavaScript n'a pas été conçu pour prendre en charge la sécurité du type statique au moment de la compilation, sans parler de qu'il est beaucoup plus pratique pour les développeurs de voir explicitement le type de fonction.
[4] : J'ai écrit des fonctions «asynchrones», en supposant qu'elles fonctionnent sous la même interface que setTimeout . Notez que les rappels ne sont pas compatibles avec les promesses et vice versa.

Source: https://habr.com/ru/post/fr474726/


All Articles