Une brève histoire des fonctionnalités asynchrones Javascript

Pendant que j'étudiais Javascript, je suis tombé sur de nombreux articles sur les fonctions et opérations asynchrones encore et encore. Malgré les avantages incontestables d'une telle fonctionnalité, à chaque fois j'ai été mis en difficulté par la cotation citée par les auteurs. Les mots ont changé, l'essence est restée la même, la bouillie se préparait dans ma tête. Under the cut - un petit guide du développement historique et des versions d'ECMA.

Pourquoi avons-nous besoin d'opérations asynchrones?


Un programme informatique peut effectuer un nombre illimité de tâches. Ce n'est un secret pour personne que les applications Web doivent fonctionner avec de nombreuses tâches différentes, qui doivent souvent utiliser les mêmes données. En particulier, l'un des exemples les plus courants est l'affichage des informations utilisateur (UI) et la récupération des informations à l'aide des demandes du serveur. Sans surprise, presque tous les développeurs Web sont confrontés à cela: travailler avec une base de données donnée, fournir une interface utilisateur, organiser une API - tout cela est littéralement dans toutes les tâches de test des programmeurs JS.

Pourquoi ne pas exécuter les commandes séquentiellement?

Souvent, les informations nécessaires à l'utilisateur ne peuvent être obtenues qu'après une période de temps considérable. Si vous organisez le programme comme:

  1. Obtenir des informations sur le site https: / some / api / item / 1
  2. Affichez des informations sur le premier élément à l'écran.

de graves difficultés surgiront avec le rendu de la page et la création d'une impression agréable sur l'utilisateur (la soi-disant expérience utilisateur). Imaginez: une page, disons, Netflix ou Aliexpress devra obtenir des données de centaines de bases de données avant de commencer à afficher le contenu à l'utilisateur. Un tel délai sera similaire au chargement d'un niveau de jeu 3D, et si le joueur est prêt à attendre, l'utilisateur du site Web souhaite obtenir le plus d'informations pour le moment.

La solution a été trouvée: les opérations asynchrones . Alors que le thread principal du programme est en train d'initialiser et d'afficher des éléments de site Web sur la toile, il génère également des tâches vers les autres threads dans l'esprit de « récupérer les marchandises pour l'utilisateur ». Dès que ce fil termine son travail, les informations «s'installent» dans le fil principal, et deviennent disponibles pour l'affichage, et sur la page Web elle-même il y a un certain espace réservé - un objet qui prend de la place pour les informations futures.

image

À ce stade, la page est déjà affichée, malgré le fait que certaines demandes ne soient pas encore passées.

image

Très probablement, quelque part au bas de la page, quelques demandes supplémentaires renvoient une valeur, et la page continue d'être mise à jour et rendue dynamiquement, sans aucun inconvénient pour l'utilisateur.

ES5 et versions antérieures: rappel


Avant de procéder à l'examen des rappels, examinons / découvrons les fonctions d'un ordre supérieur .

Une fonction d'ordre supérieur dans JS est une fonction qui prend une autre fonction comme argument . Voici un exemple:

objectIsString(objectRef) { return typeof(objectRef) === 'String'; } listOfObjects.filter(objectIsString); 

Ainsi, la fonction objectIsString a été transmise à la fonction d'ordre supérieur - filtre - qui permet de filtrer listOfObjects et de ne laisser que des objets de type chaîne dans la liste.
Les rappels fonctionnent de manière similaire. Il s'agit d'une fonction passée en argument à une autre fonction. Le plus souvent, la fonction setTimeout est utilisée comme exemple de fonction qui traite les rappels. En général, il est utilisé comme setTimeout (fonction, timeoutValue), où fonction est une fonction de rappel exécutée par le navigateur après une période spécifiée dans timeout.

 setTimeout(console.log(1), 2000); console.log(2); 


Imprimer 2 1.

ES 6: Promesses


Dans la norme 6, un nouveau type a été introduit - Promise (promesse, ci-après - Promise). Une promesse est un type dont les objets ont l'un des trois états suivants: en attente, rempli, rejeté. De plus, avec les deux derniers états, vous pouvez «associer» des fonctions - des rappels. Dès que le processus asynchrone décrit dans le cadre de la promesse elle-même aboutit / échoue, la fonction qui lui est associée sera appelée. Ce processus est appelé «rappels suspendus» et il est effectué à l'aide des méthodes then et catch de la promesse elle-même. La différence est que lorsque vous appelez ensuite les arguments, deux fonctions sont transférées - en cas de succès (onFullfillment) et d'échec (onRejected), tandis que catch accepte, comme il n'est pas difficile à deviner, uniquement une fonction pour traiter les erreurs dans une promesse. Afin de déterminer si une promesse a été exécutée avec succès dans un cas particulier, ainsi que de paramétrer le résultat renvoyé

Créons et utilisons une promesse par étapes.

 // : let promise; //     Promise. let promise = new Promise((resolve, reject) => { }); //  ,  . let promise = new Promise((resolve, reject) => { setTimeout(() => { resolve("result"); }, 1000); }); 

Ajoutez maintenant des gestionnaires d'événements à l'aide de la méthode then. L'argument de la fonction qui gère le succès est le résultat, tandis que l'argument de la fonction pour gérer l'échec de la promesse est l'erreur.

 promise .then( result => { }, error => { } ); //     – . promise .then( result => { //  - -    resolve alert("Fulfilled: " + result); // result -  resolve }, error => { //   -    reject alert("Rejected: " + error); // error -  reject } ); 

C'est fait!

Nous décrirons donc une fois de plus le processus de création d'une promesse:

  1. Initialiser l'objet (nouvelle promesse)
  2. Nous passons la fonction de résolution et / ou de rejet comme seul argument au constructeur. Une fonction doit avoir au moins 1 opération asynchrone
  3. En utilisant les méthodes then / catch, nous ajoutons des fonctions - gestionnaires de résultats.

Générateurs. Rendement


Toujours dans la norme ES6, un nouveau type de fonction a été défini - les générateurs. Ces fonctions ont la capacité de renvoyer plusieurs fois différentes valeurs avec des appels identiques à première vue. Voyons comment ils le font et pourquoi l'utiliser.

La forme standard du générateur: function * functionName () {}. Dans le corps des fonctions elles-mêmes, le mot yield est utilisé pour renvoyer une valeur intermédiaire.

À titre d'exemple, considérons le générateur suivant:

 function* generateNumber() { yield 1; yield 2; return 3; } 

Pour le moment, le générateur est au début de son exécution. Chaque fois que la prochaine méthode de générateur est appelée, le code décrit avant le rendement (ou le retour) le plus proche sera exécuté et la valeur indiquée dans la ligne avec l'un de ces mots sera également retournée.

 Let one = generateNumber.next(); // {value: 1, done: false} 

Le prochain appel renverra de la même manière la valeur 2. Le troisième appel renverra la valeur 3 et mettra fin à l'exécution de la fonction.

 Let two = generateNumber.next(); // {value: 2, done: false} Let three = generateNumber.next(); // {value: 3, done: false} 

Malgré cela, le générateur est toujours accessible via la fonction suivante . Cependant, il renverra la même valeur: l'objet {done: true}.

ES7. Async / attente


Avec le désir de plaire aux amateurs de POO à l'aide de classes de sucre syntaxiques et d'imitation d'héritage, les créateurs d'ES7 tentent de faciliter la compréhension de javascript et pour ceux qui aiment écrire du code synchrone. À l'aide de constructions asynchrones / attendent, l'utilisateur est capable d'écrire du code asynchrone aussi similaire que possible à synchrone. Si vous le souhaitez, vous pouvez vous débarrasser des promesses récemment étudiées et réécrire le code avec des changements minimes.
Prenons un exemple:

Utiliser des promesses:

 requestBook(id) { return bookAPIHelper.getBook(id).then(book => {console.log(book)}); } 

Utilisation de async / wait.

 async requestBook(id) { Const book = await bookAPIHelper.getBook(id); Console.log(book); } 

Décrivons ce que nous avons vu:

1) Async - mot clé ajouté lors de la déclaration d'une fonction asynchrone
2) Attendre - un mot-clé ajouté lors de l'appel d'une fonction asynchrone.

ES8. Itération asynchrone


L'itération sur les données de manière synchrone est devenue possible dans ES5. Après deux spécifications, il a été décidé d'ajouter la possibilité d'une itération asynchrone fonctionnant dans des sources de données asynchrones. Maintenant, lorsque next () est appelé, il ne renverra pas {value, done}, mais une promesse (voir ES6).

Examinons la fonction createAsyncIterable (itérable).

 async function* createAsyncIterable(iterable) { for (const elem of iterable) { yield elem; } } 

Comme vous pouvez le voir, la fonction initialise la collection, pour chaque appel aux éléments dont une promesse sera retournée avec la valeur spécifiée dans itérable.

 const asyncIterable = createAsyncIterable(['async 1', 'async 2']); const asyncIterator = asyncIterable[Symbol.asyncIterator](); asyncIterator.next() .then(result => { console.log(result); // { // value: 'async 1', // done: false, // } return asyncIterator.next(); }) .then(result => { console.log(result); // { // value: 'async 2', // done: false, // } return asyncIterator.next(); }) .then(result => { console.log(result); // { // value: 'undefined', // done: true, // } }); 

De plus, la nouvelle norme définit une boucle d'attente qui convient à de telles opérations.

 for await (const x of createAsyncIterable(['a', 'b'])) 

TL; DR


Il n'est pas du tout nécessaire de connaître et de mémoriser par cœur à quelle version d'ECMAScript telle ou telle syntaxe appartient, surtout si vous venez de commencer à vous familiariser avec le comportement asynchrone dans JS. Dans le même temps, l'étude de l'asynchronie dans l'ordre exactement proposé par l'histoire du développement des spécifications permettra au programmeur de comprendre parfaitement la syntaxe et les instructions transmises au moteur JS, mais aussi de suivre la logique d'amélioration d'ECMAScript en tant que produit, de comprendre les tendances dictées par les développeurs JS, de les séparer et d'accepter .

Bref, alors:

Rappels <= ES5
Promesses, rendement (générateurs): ES6
Async / attente: ES7
Itérateurs asynchrones: ES8

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


All Articles