Programmation événementielle ce que l'async a sur la synchronisation

L'un des points forts de JavaScript est la manière dont il gère le code asynchrone (en abrégé asynchrone). Plutôt que de bloquer le thread, le code asynchrone est poussé vers une file d'attente d'événements qui se déclenche après l'exécution de tout autre code. Cependant, il peut être difficile pour les débutants de suivre le code asynchrone. Je vais aider à dissiper toute confusion que vous pourriez avoir dans cet article.


Comprendre le code async

Les fonctions asynchrones les plus élémentaires de JavaScript sont setTimeout et setInterval. le setTimeout function exécute une fonction donnée après un certain temps. Il accepte une fonction de rappel comme premier argument et une heure (en millisecondes) comme second argument. Voici un exemple de son utilisation:

 console.log ("a"); setTimeout (function () console.log ("c"), 500); setTimeout (function () console.log ("d"), 500); setTimeout (function () console.log ("e"), 500); console.log ("b");

Comme prévu, la console affiche "a", "b", puis 500 ms (ish) plus tard, nous voyons "c", "d" et "e". J'utilise "ish" parce que setTimeout est réellement imprévisible. En fait, même la spécification HTML5 parle de ce problème:

"Cette API ne garantit pas que les minuteries fonctionneront exactement comme prévu. Des retards dus à la charge du processeur, à d'autres tâches, etc., sont à prévoir."

Fait intéressant, un délai d'attente ne sera pas exécuté tant que tout le code restant dans un bloc n'aura pas été exécuté. Donc, si un délai d'attente est défini et qu'une fonction longue en cours d'exécution s'exécute, le délai d'attente ne démarrera même pas avant la fin de cette fonction en cours d'exécution longue. En réalité, async fonctionne comme setTimeout et setInterval sont poussés sur une file d'attente connue sous le nom Boucle d'événement.

le Boucle d'événement est une file d'attente de fonctions de rappel. Lorsqu'une fonction asynchrone s'exécute, la fonction de rappel est poussée dans la file d'attente. Le moteur JavaScript ne commence pas à traiter la boucle d'événements avant le code après l'exécution d'une fonction asynchrone. Cela signifie que le code JavaScript n'est pas multithread même s'il semble en être ainsi. La boucle d'événements est une file d'attente premier entré, premier sorti (FIFO), ce qui signifie que les rappels s'exécutent dans l'ordre dans lequel ils ont été ajoutés à la file d'attente. JavaScript a été choisi pour le langage de noeud en raison de la facilité d'écriture de ce type de code.


Ajax

JavaScript asynchrone et XML (Ajax) ont changé à jamais le paysage de JavaScript. Tout d'un coup, un navigateur pouvait mettre à jour une page Web sans avoir à le recharger. Le code pour implémenter Ajax dans différents navigateurs peut être long et fastidieux à écrire; Cependant, grâce à jQuery (et à d’autres bibliothèques), Ajax est devenu une solution extrêmement simple et élégante pour faciliter la communication client-serveur..

Récupération asynchrone de données avec jQuery $ .ajax est un processus facile entre plusieurs navigateurs, mais il n’est pas immédiatement évident de savoir ce qui se passe exactement dans les coulisses. Par exemple:

données var; $ .ajax (url: "some / url / 1", succès: function (data) // Mais ça va! console.log (data);) // Oups, ça ne marchera pas… console .log (données);

Il est courant, mais incorrect, de supposer que les données sont disponibles immédiatement après l'appel. $ .ajax, mais ce qui se passe réellement est la suivante:

xmlhttp.open ("GET", "some / ur / 1", true); xmlhttp.onreadystatechange = fonction (données) if (xmlhttp.readyState === 4) console.log (données); ; xmlhttp.send (null);

Le sous-jacent XmlHttpRequest (XHR) objet envoie la demande, et la fonction de rappel est définie pour gérer le XHR readystatechange un événement. Puis les XHR envoyer méthode s'exécute. Au fur et à mesure que la XHR accomplit son travail, un readystatechange événement se déclenche chaque fois que le état prêt la propriété change et la fonction de rappel ne s'exécute que lorsque XHR a fini de recevoir une réponse de l'hôte distant.


Travailler avec du code async

La programmation asynchrone se prête à ce qu'on appelle communément "l'enfer de rappel". Étant donné que pratiquement toutes les fonctions asynchrones de JavaScript utilisent des rappels, l'exécution de plusieurs fonctions asynchrones séquentielles génère de nombreux rappels imbriqués, ce qui rend le code difficile à lire..

La plupart des fonctions de node.js sont asynchrones. Donc, un code comme celui-ci est assez courant.

var fs = require ("fs"); fs.exists ("index.js", function () fs.readFile ("index.js", "utf8", function (err, contenu) contents = someFunction (contents); // fait quelque chose avec le contenu fs. writeFile ("index.js", "utf8", function () console.log ("ouf! fait enfin ...");););); console.log ("exécuter…");

Il est également courant de voir le code côté client, comme suit:

GMaps.geocode (adresse: fromAddress, callback: fonction (résultats, status) if (status == "OK") fromLatLng = results [0] .geometry.location; GMaps.geocode (adresse: toAddress, callback: fonction (résultats, état) if (état == "OK") toLatLng = résultats [0] .geometry.location; map.getRoutes (origine: [fromLatLng.lat (), fromLatLng.lng ()], destination : [toLatLng.lat (), toLatLng.lng ()], travelMode: "driving", unitSystem: "imperial", callback: function (e) console.log ("ANNNND FINALLY voici les instructions…"); // faire quelque chose avec e);););

Les callbacks imbriqués peuvent être vraiment méchants, mais il y a plusieurs solutions à ce style de codage.

Le problème ne vient pas de la langue elle-même. c'est avec la façon dont les programmeurs utilisent le langage - Async Javascript.

Fonctions nommées

Une solution simple qui nettoie les rappels imbriqués consiste simplement à éviter d'imbriquer plus de deux niveaux. Au lieu de transmettre des fonctions anonymes aux arguments de rappel, transmettez une fonction nommée:

var fromLatLng, toLatLng; var routeDone = function (e) console.log ("ANNNND FINALLY, voici les instructions…"); // faire quelque chose avec e; var toAddressDone = function (résultats, statut) if (status == "OK") toLatLng = results [0] .geometry.location; map.getRoutes (origine: [fromLatLng.lat (), fromLatLng.lng ()], destination: [toLatLng.lat (), toLatLng.lng ()], travelMode: "conduite", unitSystem: "imperial", rappel : routeDone); ; var fromAddressDone = function (résultats, statut) if (status == "OK") fromLatLng = results [0] .geometry.location; GMaps.geocode (adresse: toAddress, rappel: toAddressDone); ; GMaps.geocode (adresse: fromAddress, rappel: fromAddressDone);

En outre, la bibliothèque async.js peut vous aider à gérer plusieurs demandes / réponses Ajax. Par exemple:

async.parallel ([function (done) GMaps.geocode (adresse: toAddress, rappel: function (résultat) done (null, résultat););, function (done) GMaps.geocode (adresse : fromAddress, callback: fonction (résultat) done (résultat, null););], fonction (erreurs, résultats) getRoute (résultats [0], résultats [1]););

Ce code exécute les deux fonctions asynchrones et chaque fonction accepte un rappel "terminé" qui s'exécute après l'exécution de la fonction asynchrone. Lorsque les deux rappels "done" sont terminés, le parallèle le rappel de la fonction exécute et gère les erreurs ou les résultats des deux fonctions asynchrones.

Promesses

De la CommonJS / A:

Une promesse représente la valeur éventuelle de l’achèvement unique d’une opération..

Le modèle de promesse est intégré dans de nombreuses bibliothèques et les utilisateurs de jQuery disposent déjà d’une API de promesse intéressante. jQuery a présenté le Différé objet dans la version 1.5, et en utilisant le jQuery.Deferred Le constructeur donne une fonction qui retourne une promesse. Une fonction de retour de promesse effectue une sorte d'opération asynchrone et résout le différé à la fin..

var geocode = fonction (adresse) var dfd = new $ .Deferred (); GMaps.geocode (adresse: adresse, rappel: fonction (réponse, état) return dfd.resolve (réponse);); retourne dfd.promise (); ; var getRoute = fonction (fromLatLng, toLatLng) var dfd = new $ .Deferred (); map.getRoutes (origine: [fromLatLng.lat (), fromLatLng.lng ()], destination: [toLatLng.lat (), toLatLng.lng ()], travelMode: "conduite", unitSystem: "imperial", rappel : fonction (e) return dfd.resolve (e);); retourne dfd.promise (); ; var doSomethingCoolWithDirections = function (route) // faire quelque chose avec route; $ .when (géocode (fromAddress), géocode (toAddress)). then (function (fromLatLng, toLatLng) getRoute (fromLatLng, toLatLng) .then (doSomethingCoolWithDirections););

Cela vous permet d'exécuter deux fonctions asynchrones, d'attendre leurs résultats, puis d'exécuter une autre fonction avec les résultats des deux premiers appels..

Une promesse représente la valeur éventuelle de l’achèvement unique d’une opération..

Dans ce code, le géocodage La méthode s'exécute deux fois et retourne une promesse. Les fonctions asynchrones s'exécutent et appellent résoudre dans leurs rappels. Puis, une fois que les deux ont appelé résoudre, la puis s'exécute en renvoyant les résultats des deux premiers appels à géocodage. Les résultats sont ensuite transmis à getRoute, qui retourne également une promesse. Enfin, lorsque la promesse de getRoute est résolu, le quelque chose de cool avec les directives le rappel s'exécute.

Événements

Les événements sont une autre solution pour communiquer lorsque les rappels asynchrones ont fini de s'exécuter. Un objet peut devenir un émetteur et publier des événements que d'autres objets peuvent écouter. Ce type de concours est appelé le modèle d'observateur. La bibliothèque backbone.js a ce type de fonctionnalité intégrée avec Backbone.Events.

var SomeModel = Backbone.Model.extend (url: "/ someurl"); var SomeView = Backbone.View.extend (initialize: function () this.model.on ("reset", this.render, this); this.model.fetch ();, rendu: fonction (données)  // faire quelque chose avec les données); var view = new SomeView (modèle: new SomeModel ());

Il existe d'autres exemples et bibliothèques de mixage permettant d'émettre des événements, tels que jQuery Event Emitter, EventEmitter, monologue.js et node.js intègre un module EventEmitter..

La boucle d'événement est une file d'attente de fonctions de rappel.

Une méthode similaire de publication de messages utilise le modèle de médiateur, utilisé dans la bibliothèque postal.js. Dans le modèle médiateur, un intermédiaire pour tous les objets écoute et publie des événements. Dans cette approche, un objet ne fait pas directement référence à un autre, découplant ainsi les objets les uns des autres.

Ne retournez jamais une promesse via une API publique. Cela permet aux utilisateurs d’API d’utiliser leurs promesses et rend le refactoring difficile. Cependant, une combinaison de promesses à des fins internes et d'événements pour des API externes peut conduire à une application découplée et testable..

Dans l'exemple précédent, le quelque chose de cool avec les directives fonction de rappel s'exécute lorsque les deux précédents géocodage les fonctions sont terminées. le quelque chose de cool avec les directives peut alors prendre la réponse reçue getRoute et publier la réponse sous forme de message.

var doSomethingCoolWithDirections = fonction (route) postal.channel ("ui") .publish ("directions.done", route: route); ;

Cela permet à d'autres zones de l'application de répondre au rappel asynchrone sans avoir besoin d'une référence directe à l'objet de requête. Il est possible que plusieurs zones d'une page nécessitent une mise à jour lorsque les instructions ont été récupérées. Dans une configuration jQuery Ajax typique, le rappel de réussite doit être ajusté lors de la réception d'un changement de direction. Cela peut devenir difficile à gérer, mais en utilisant la messagerie, il est beaucoup plus facile de mettre à jour plusieurs parties de l'interface utilisateur..

var UI = function () this.channel = postal.channel ("ui"); this.channel.subscribe ("directions.done", this.updateDirections) .withContext (this); ; UI.prototype.updateDirections = function (data) // La route est disponible sur data.route, il suffit maintenant de mettre à jour l'interface utilisateur; app.ui = new UI ();

Certaines autres bibliothèques de messagerie basées sur un modèle médiateur sont amplify, PubSubJS et radio.js..


Conclusion

JavaScript rend l'écriture de code async très facile. Utiliser des promesses, des événements ou des fonctions nommées élimine le méchant "enfer de rappel". Pour plus d'informations sur JavaScript asynchrone, consultez JavaScript asynchrone: créez des applications plus réactives avec moins de code. La plupart des exemples de cet article résident dans un référentiel Github appelé NetTutsAsyncJS. Cloner!