Passer au contenu principal




La parte central de un ordenador, o être, la parte que lleva a cabo los pasos individuales que componen nuestros programas, se llama processeur. Les émissions que nous avons vues jusqu'à présent sont des choses qui garderont le processeur jusqu'à ce qu'ils aient terminé leur travail. La vitesse à laquelle quelque chose comme une boucle manipulant des nombres peut être exécuté dépend fortement de la vitesse du processeur.

Mais de nombreux programmes interagissent avec des éléments externes au processeur. Par exemple, ils peuvent communiquer sur un réseau informatique ou demander des données sur le disque dur, ce qui est beaucoup plus lent que de les obtenir à partir de la mémoire.

Quand quelque chose comme ça se produit, il serait dommage de laisser le processeur inactif car il pourrait y avoir un autre travail à faire entre-temps. Cela est en partie géré par votre système d'exploitation, qui fera basculer le processeur entre plusieurs programmes en cours d'exécution. Mais cela n'aide pas lorsque nous voulons qu'un seul programme puisse progresser en attendant une requête réseau.

Asynchronie

En un modelo de programmation sincrónica, las cosas suceden una a la vez. Cuando se llama a una función que realiza una acción de larga duración, sólo vuelve cuando la acción ha finalizado y puede devolver el resultado. Esto detiene el programa durante el tiempo que dure la acción.

Un modèle asynchrone permet à plusieurs choses de se produire en même temps. Lorsque vous démarrez une action, le programme continue de s'exécuter. Lorsque l'action se termine, le programme est informé et a accès au résultat (par exemple, données lues à partir du disque).

Nous pouvons comparer la programmation synchrone et asynchrone à l'aide d'un petit exemple: un programme qui obtient deux ressources du réseau et combine ensuite les résultats.

Dans un environnement synchrone, où la fonction de requête ne revient qu'après avoir effectué son travail, le moyen le plus simple d'accomplir cette tâche consiste à effectuer les requêtes les unes après les autres. Cela présente l'inconvénient que la deuxième demande ne démarre que lorsque la première est terminée. Le temps total requis sera au moins la somme des deux temps de réponse.

La solution à ce problème, dans un système synchrone, consiste à exécuter des threads de contrôle supplémentaires. Un thread est un autre programme en cours d'exécution dont l'exécution peut être entrelacée avec d'autres programmes par le système d'exploitation; Étant donné que la plupart des ordinateurs modernes contiennent plusieurs processeurs, plusieurs threads peuvent même s'exécuter en même temps, sur différents processeurs. Un deuxième thread peut démarrer la deuxième requête, puis les deux threads attendent que leurs résultats reviennent, après quoi ils se resynchronisent pour combiner leurs résultats.

Dans le modèle synchrone, le temps pris par le réseau fait partie de la chronologie d'un thread de contrôle donné. Dans le modèle asynchrone, le début d'une action réseau provoque conceptuellement une scission sur la chronologie. Le programme qui a lancé l'action continue de s'exécuter, et l'action se produit à côté de lui, notifiant le programme quand il se termine.

Une autre façon de décrire la différence est que l'attente de la fin des actions est implicite dans le modèle synchrone, alors que c'est explicite, sous notre contrôle, dans le modèle asynchrone.

Asynchronie courte dans les deux sens. Cela facilite l'expression de programmes qui ne correspondent pas au modèle de contrôle en ligne droite, mais cela peut également rendre les programmes d'expression qui suivent une ligne droite plus encombrants. Nous examinerons quelques moyens de remédier à cet inconfort plus loin dans ce chapitre.

Las dos importantes plataformas de programación JavaScript (navigateurs y Node.js) realizan operaciones que pueden tardar un tiempo en ser asincrónicas, en lugar de depender de sub-procesos. Dado que la programación con hilos es notoriamente difícil (entender lo que hace un programa es mucho más difícil cuando está haciendo múltiples cosas a la vez), esto generalmente se considera algo bueno.

Technologie Crow

La plupart des gens savent que les corbeaux sont des oiseaux très intelligents. Ils peuvent utiliser des outils, planifier à l'avance, se souvenir des choses et même communiquer entre eux.

Ce que la plupart des gens ne savent pas, c'est qu'ils sont capables de beaucoup de choses qu'ils nous cachent bien. Un expert réputé (quoique quelque peu excentrique) des corvidés m'a dit que la technologie des corbeaux n'est pas loin en dessous de la technologie humaine, et qu'ils rattrapent leur retard.

Par exemple, de nombreuses cultures de corbeaux ont la capacité de construire des appareils informatiques. Ceux-ci ne sont pas électroniques, comme le sont les appareils informatiques humains, mais fonctionnent plutôt grâce aux actions de minuscules insectes, une espèce étroitement liée au termite, qui a développé une relation symbiotique avec les corbeaux. Les oiseaux leur fournissent de la nourriture et, en retour, les insectes construisent et exploitent leurs colonies complexes qui, avec l'aide des êtres vivants à l'intérieur, effectuent des calculs.

Ces colonies sont généralement situées dans de grands nids à longue durée de vie. Les oiseaux et les insectes travaillent ensemble pour construire un réseau de structures d'argile bulbeuses, cachées parmi les brindilles du nid, dans lesquelles les insectes vivent et travaillent.

Pour communiquer avec d'autres appareils, ces machines utilisent des signaux lumineux. Les corbeaux incorporent des morceaux de matériau réfléchissant dans des tiges de communication spéciales, et les insectes les dirigent pour réfléchir la lumière dans un autre nid, codant les données comme une séquence d'éclairs rapides. Cela signifie que seuls les nids qui ont une connexion visuelle ininterrompue peuvent communiquer.

Notre ami l'expert des corvidés a cartographié le réseau de nids de corbeaux dans la ville de Hières-sur-Amby, sur les rives du Rhône.

Dans un exemple étonnant d'évolution convergente, les ordinateurs corbeaux exécutent JavaScript. Dans ce chapitre, nous leur écrirons quelques fonctions réseau de base.

Rappels

Une approche de la programmation asynchrone consiste à faire en sorte que les fonctions à action lente prennent un argument supplémentaire, une fonction de rappel. L'action démarre et lorsqu'elle se termine, la fonction de rappel est appelée avec le résultat.

Par exemple, la fonction setTimeout, disponible à la fois en Node.js comme dans le navigateurs, il attend un nombre spécifié de millisecondes (une seconde équivaut à mille millisecondes) puis appelle une fonction.

setTimeout (() => console.log ("Tick"), 500);

Habituellement, attendre n'est pas un type de travail très important, mais cela peut être utile pour faire quelque chose comme mettre à jour une animation ou vérifier si quelque chose prend plus de temps qu'un certain temps.

L'exécution de plusieurs actions asynchrones sur une ligne à l'aide de la fonction de rappel signifie que vous devez continuer à passer de nouvelles fonctions pour gérer la poursuite du calcul après les actions.

La plupart des ordinateurs nid de Corbeau Ils ont une ampoule de stockage de données à long terme, où des informations sont gravées sur des brindilles afin qu'elles puissent être récupérées plus tard. L'enregistrement ou la recherche de données prend un moment, de sorte que l'interface pour le stockage à long terme est asynchrone et utilise des fonctions de rappel.

Las bombillas de almacenamiento almacenan los datos codificados por JSON bajo nombres. Un cuervo puede almacenar información sobre los lugares donde se esconde la comida bajo el nombre de «cachés de comida», que puede contener una serie de nombres que apuntan a otras piezas de datos, describiendo la cache real. Para buscar un caché de comida en los bulbos de almacenamiento del nido de roble grande, un cuervo podría ejecutar un código como este:

import {bigOak} de "./crow-tech"; bigOak.readStorage ("caches alimentaires", caches => {let firstCache = caches [0]; bigOak.readStorage (firstCache, info => {console.log (info);});});

(Tous les noms et chaînes ont été traduits de la langue Crow vers l'anglais.

Ce style de programmation est faisable, mais le niveau d'indentation augmente à chaque action asynchrone car il se termine par une autre fonction. Faire des choses plus compliquées, comme exécuter plusieurs actions en même temps, peut être un peu gênant.

Les ordinateurs Crow's Nest sont conçus pour communiquer à l'aide de paires demande-réponse. Cela signifie qu'un nid envoie un message à un autre nid, qui renvoie immédiatement un message, en accusant réception et éventuellement en incluant une réponse à une question posée dans le message.

Chaque message est étiqueté avec un type, qui détermine la façon dont il est traité. Notre code peut définir des gestionnaires pour des types spécifiques de demandes, et lorsqu'une demande de ce type est reçue, le gestionnaire est appelé pour produire une réponse.

L'interface exportée par le module "./Crow-tech" proporciona funciones basadas en la callback para la comunicación. Los nidos tienen un método de envío que envía una petición. Espera el nombre del nido de destino, el tipo de solicitud y el Contenu de la solicitud como sus tres primeros argumentos, y espera que una función llame cuando se reciba una respuesta como su cuarto y último argumento.

bigOak.send ("Cow Pasture", "note", "Allons croquer fort à 19h", () => console.log ("Note délivrée."));

Mais pour rendre les nids capables de recevoir cette requête, il faut d'abord définir un type de requête appelé «note». Le code qui gère les requêtes doit être exécuté non seulement dans ce nid d'ordinateurs mais dans tous les nids pouvant recevoir des messages de ce type. Nous supposerons qu'un corbeau vole et installe notre code dans tous les nids.

import {defineRequestType} de "./crow-tech"; defineRequestType ("note", (nest, content, source, done) => {console.log (`$ {nest.name} a reçu note: $ {contenu}`); done ();});

La fonction defineRequestType définit un nouveau type de demande. L'exemple ajoute la prise en charge des requêtes "note", qui envoient simplement une note à un nid donné. Nos appels de mise en œuvre console.log afin que nous puissions vérifier que la demande est arrivée. Les nids ont une propriété avec un nom qui contient leur nom.

Le quatrième argument donné au gestionnaire, done, est une fonction de rappel qu'il doit appeler lorsque la requête se termine. Si nous avions utilisé la valeur de retour du contrôleur comme valeur de réponse, cela signifierait qu'un contrôleur de requête ne peut pas effectuer d'actions asynchrones. Une fonction qui effectue un travail asynchrone revient normalement avant la fin du travail, après avoir organisé un rappel à appeler une fois terminé. Nous avons donc besoin d'un mécanisme asynchrone (dans ce cas, une autre fonction de rappel) pour signaler lorsqu'une réponse est disponible.

D'une certaine manière, l'asynchronie est contagieuse. Toute fonction qui appelle une fonction qui fonctionne de manière asynchrone doit être asynchrone, en utilisant un mécanisme de rappel ou autre pour fournir son résultat. Appeler un rappel est un peu plus compliqué et sujet aux erreurs que de simplement renvoyer une valeur, il n'est donc pas bon d'avoir à structurer de grandes parties de votre programme de cette façon.

Promesses

Trabajar con conceptos abstractos es a menudo más fácil cuando esos conceptos pueden ser representados por valores. En el caso de acciones asincrónicas, en lugar de organizar la llamada de una función en algún momento futuro, se puede devolver un objeto que represente este un événement futuro.

C'est à cela que sert la classe Promise standard. Une promesse est une action asynchrone qui peut se terminer à un moment donné et produire une valeur. Il est en mesure d'avertir toute personne intéressée lorsque sa valeur est disponible.

Le moyen le plus simple de créer une promesse est d'appeler Promise.resolve. Cette fonction garantit que la valeur qui lui est donnée est enveloppée dans une promesse. Si c'est déjà une promesse, elle est simplement retournée; sinon, vous obtenez une nouvelle promesse qui met immédiatement fin à sa valeur.

soit quinze = Promise.resolve (15); fifteen.then (value => console.log (`Got $ {value}`)); // → J'ai 15

Pour obtenir le résultat d'une promesse, vous pouvez utiliser sa méthode then. Cela enregistre une fonction de rappel à appeler lorsque la promesse se résout et produit une valeur. Vous pouvez ajouter plusieurs rappels à une seule promesse, et ils seront appelés, même si vous les ajoutez une fois que la promesse a déjà été résolue (terminée).

Mais ce n'est pas tout ce que fait alors la méthode. Renvoie une autre promesse, qui résout la valeur renvoyée par la fonction de contrôleur ou, si elle renvoie une promesse, attend cette promesse, puis résout son résultat.

Il est utile de considérer les promesses comme un dispositif permettant de traduire des valeurs en réalité asynchrone. Une valeur normale est juste là. Un titre promis est un titre qui peut déjà exister ou qui peut apparaître à un moment donné dans le futur. Les calculs définis en termes de promesses agissent sur ces valeurs encapsulées et s'exécutent de manière asynchrone lorsque les valeurs deviennent disponibles.

Pour créer une promesse, vous pouvez utiliser Promise en tant que constructeur. Il a une interface un peu étrange: le constructeur attend une fonction comme argument, qu'il appelle immédiatement, en lui passant une fonction qu'il peut utiliser pour résoudre la promesse. Cela fonctionne de cette façon, plutôt que, par exemple, avec une méthode de résolution, de sorte que seul le code qui a créé la promesse puisse la résoudre.

Voici comment créer une interface basée sur des promesses pour la fonction readStorage:

fonction stockage (nest, nom) {retourne une nouvelle promesse (résoudre => {nest.readStorage (nom, résultat => résoudre (résultat));}); } stockage (bigOak, "ennemis") .then (valeur => console.log ("Got", valeur));

Cette fonction asynchrone renvoie une valeur significative. C'est le principal avantage des promesses: elles simplifient l'utilisation des fonctions asynchrones. Au lieu d'avoir à passer des rappels, les fonctions basées sur la promesse ressemblent aux fonctions normales: elles prennent les données comme arguments et renvoient leurs résultats. La seule différence est que la sortie n'est peut-être pas encore disponible.

Échec

Les calculs JavaScript normaux peuvent échouer si une exception est faite. Les calculs asynchrones ont souvent besoin de quelque chose comme ça. Une demande réseau peut échouer ou un code qui fait partie du calcul asynchrone peut être une exception.

L'un des problèmes les plus urgents avec le style de rappel de la programmation asynchrone est qu'il est extrêmement difficile de garantir que les erreurs sont correctement signalées aux rappels.

Une convention largement utilisée est que le premier argument du rappel est utilisé pour indiquer que l'action a échoué, et le second contient la valeur produite par l'action lorsqu'elle a réussi. Ces fonctions de rappel doivent toujours rechercher une exception et s'assurer que tous les problèmes qu'elles provoquent, y compris les exceptions levées par les fonctions qu'elles appellent, sont interceptées et affectées à la fonction correcte.

Les promesses facilitent les choses. Ils peuvent être résolus (l'action réussie) ou rejetée (échec). Les gestionnaires de résolution (tels qu'inscrits à ce moment-là) sont appelés uniquement lorsque l'action est réussie et les rejets sont automatiquement propagés à la nouvelle promesse qui est renvoyée d'ici là. Et lorsqu'un gestionnaire lève une exception, il provoque automatiquement le rejet de la promesse produite par son appel. Ainsi, si un élément d'une chaîne d'actions asynchrones échoue, le résultat de la chaîne entière est marqué comme rejeté et aucun gestionnaire de succès n'est appelé au-delà du point auquel il a échoué.

De la même manière que résoudre une promesse fournit une valeur, en rejeter une en fournit également une, généralement appelée la raison du rejet. Lorsqu'une exception dans une fonction de gestionnaire entraîne un rejet, la valeur d'exception est utilisée comme raison. De même, lorsqu'un gestionnaire renvoie une promesse rejetée, ce rejet se répercute sur la promesse suivante. Il y a une fonction Promise.reject ce qui crée une nouvelle promesse, immédiatement rejetée.

Pour gérer explicitement de tels rejets, les promesses ont une méthode catch qui enregistre un gestionnaire à appeler lorsque la promesse est rejetée, de la même manière que les gestionnaires gèrent la résolution normale. C'est aussi très similaire en ce sens qu'il renvoie une nouvelle promesse, qui se résout à la valeur de la promesse d'origine si elle se résout normalement, et au résultat du gestionnaire catch dans le cas contraire. Si un gestionnaire de capture renvoie une erreur, la nouvelle promesse est également rejetée.

En bref, il accepte également un gestionnaire de rejet comme deuxième argument, vous pouvez donc installer les deux types de gestionnaires en un seul appel de méthode.

Une fonction transmise au constructeur Promise reçoit un deuxième argument, avec la fonction de résolution, qu'elle peut utiliser pour rejeter la nouvelle promesse.

Les chaînes de valeurs de promesse créées par les appels à ce stade et la capture peuvent être considérées comme un pipeline à travers lequel les valeurs asynchrones ou les échecs se déplacent. Étant donné que ces chaînes sont créées en enregistrant des gestionnaires, chaque lien a un gestionnaire de succès ou un gestionnaire de rejet (ou les deux) qui lui est associé. Les gestionnaires qui ne correspondent pas au type de résultat (succès ou échec) sont ignorés. Mais ceux qui correspondent sont appelés, et leur résultat détermine quel type de valeur vient ensuite: succès quand il retourne une valeur sans promesse, rejet quand il lance une exception, et le résultat d'une promesse quand il en renvoie une.

nouvelle promesse ((_, rejeter) => rejeter (nouvelle erreur ("Fail"))) .then (value => console.log ("Handler 1")) .catch (reason => {console.log ("Caught échec "+ raison); retourne" rien ";}) .then (valeur => console.log (" Handler 2 ", valeur)); // → Erreur d'erreur interceptée: Échec // → Handler 2 rien

Tout comme une exception non interceptée est gérée par l'environnement, les environnements JavaScript peuvent détecter lorsqu'un rejet de promesse n'est pas géré et le signalera comme une erreur.