La "boucle d'événement" de Node est le cœur de sa capacité à gérer une grande concurrence et un débit élevé. C'est l'endroit le plus magique. Selon cela, Node.js peut essentiellement être compris comme "un seul lancement", et il permet également de traiter des opérations arbitraires en arrière-plan. Cet article illustrera le fonctionnement des boucles d'événements et vous pouvez également ressentir sa magie.
Programmation axée sur des événements
Pour comprendre les boucles d'événements, nous devons d'abord comprendre la programmation du lecteur d'événements. Il est apparu en 1960. Aujourd'hui, la programmation axée sur les événements est largement utilisée dans la programmation d'interface utilisateur. L'une des principales utilisations de JavaScript est d'interagir avec le DOM, il est donc naturel d'utiliser une API basée sur des événements.
Définissez simplement: la programmation axée sur les événements contrôle le processus d'application par des modifications des événements ou des états. Il est généralement mis en œuvre par la surveillance des événements. Une fois l'événement détecté (c'est-à-dire que l'état change), la fonction de rappel correspondante est appelée. Cela semble familier? En fait, il s'agit du principe de travail de base de la boucle d'événement Node.js.
Si vous connaissez le développement JavaScript côté client, pensez à ces méthodes .on * (), telles que Element.OnClick (), qui sont utilisées pour combiner avec des éléments DOM pour passer l'interaction utilisateur. Ce mode de travail permet de déclencher plusieurs événements sur une seule instance. Node.js déclenche ce mode via EventEmitter (générateur d'événements), comme dans le socket et les modules "http" du côté du serveur. Un ou plusieurs changements d'état peuvent être déclenchés à partir d'une seule instance.
Un autre modèle commun est d'exprimer le succès et l'échec. Il existe généralement deux méthodes de mise en œuvre courantes maintenant. La première chose consiste à passer «l'exception d'erreur» dans le rappel, qui est généralement transmis à la fonction de rappel comme premier paramètre. Le deuxième type consiste à utiliser le mode de conception des promesses, et ES6 a été ajouté. Remarque * Le mode Promise utilise une méthode d'écriture de chaîne de fonction de type jQuery pour éviter la nidification de fonction de rappel profonde, telle que:
La copie de code est la suivante:
$ .getjson ('/ getUser'). Done (SuccessHandler) .fail (failhandler)
Les modules "FS" (Système de fichiers) utilisent principalement le style de passage des exceptions dans le rappel. Déclenchant techniquement certains appels, tels que Fs.ReadFile () Jumed Event, mais l'API est uniquement utilisée pour rappeler à l'utilisateur d'exprimer le succès ou l'échec de l'opération. Une telle API est choisie pour des raisons architecturales, et non des limitations techniques.
Une idée fausse commune est que les émetteurs d'événements sont également intrinsèquement asynchrones lors du déclenchement des événements, mais cela est incorrect. Voici un simple extrait de code pour le prouver.
La copie de code est la suivante:
function myemitte () {
EventEmitter.Call (this);
}
util.inherits (Myemitter, eventEmitter);
Myemiter.prototype.dostuff = fonction Dostuff () {
console.log ('avant')
emiter.emit ('feu')
console.log ('After')}
};
var me = new MyeMitter ();
me.on ('feu', function () {
console.log («émit tiré»);
});
me.dostuff ();
// Sortir:
// avant
// émet un licenciement
// après
Remarque * Si Emiter.Emit est asynchrone, la sortie doit être
// avant
// après
// émet un licenciement
EventEmitter se manifeste souvent asynchrone car il est souvent utilisé pour informer les opérations qui doivent être terminées de manière asynchrone, mais l'API EventEmitter lui-même est entièrement synchrone. La fonction d'écoute peut être exécutée de manière asynchrone, mais veuillez noter que toutes les fonctions d'écoute seront exécutées de manière synchrone dans l'ordre où elles sont ajoutées.
Présentation du mécanisme et regroupement de threads
Le nœud lui-même repose sur plusieurs bibliothèques. L'un d'eux est Libuv, une bibliothèque qui gère comme par magie les files d'attente d'événements asynchrones et les exécutions.
Node utilise autant de fonctions existantes que possible pour utiliser le noyau du système d'exploitation. Par exemple, une demande de réponse est générée, les connexions sont transmises et déléguées au système de traitement. Par exemple, les connexions entrantes sont en file d'attente via le système d'exploitation jusqu'à ce qu'ils puissent être traités par nœud.
Vous avez peut-être entendu dire que le nœud a une piscine de fil, et vous vous demandez peut-être: "Si le nœud gère les tâches dans l'ordre, pourquoi avez-vous besoin d'une piscine de fil?" En effet, dans le noyau, toutes les tâches ne sont pas exécutées de manière asynchrone. Dans ce cas, Node.js doit être en mesure de verrouiller le fil pendant une période pendant qu'il fonctionne afin qu'il puisse continuer à exécuter la boucle d'événement sans être bloqué.
Ce qui suit est un exemple de diagramme simple pour montrer son mécanisme de fonctionnement interne:
┌─ase
Ui
│ └fique
│ ┌fiques
│ │ Rappels en attente │
│ └fiques
│ ┌fiques
│ │ sondage │◄fiques
│ └fiques
│ ┌fique
──┤ Settimmediate │
└──formation
Il y a des difficultés à comprendre le mécanisme de fonctionnement interne de la boucle d'événement:
Tous les rappels sont prédéfinis via Process.NextTick () avant la fin d'une phase de la boucle d'événement (par exemple, une minuterie) et passez à la phase suivante. Cela évitera l'appel récursif potentiel de processus.NextTick (), provoquant une boucle infinie.
"Rappel en attente" est un rappel dans la file d'attente de rappel qui ne sera traité par aucun autre cycle de boucle d'événements (par exemple, transmis à Fs.Write).
Émetteur d'événements et boucle d'événement
En créant EventEmitter, l'interaction avec les boucles d'événements peut être simplifiée. Il s'agit d'une encapsulation universelle qui vous permet de créer plus facilement des API basées sur des événements. Comment les deux interagissent souvent les développeurs se sentent confus.
L'exemple suivant montre que l'oubli que l'événement est déclenché de manière synchrone peut entraîner le manque de l'événement.
La copie de code est la suivante:
// après v0.10, require ('événements'). EventEmitter n'est plus nécessaire
var eventEMmitter = require ('Events');
var util = require ('util');
fonction mything () {
EventEmitter.Call (this);
Dofirstthing ();
this.emit ('thing1');
}
util.inherits (mything, eventEmitter);
var mt = new Mything ();
Mt.on ('Thing1', fonction Onthing1 () {
// désolé, cet incident n'arrivera jamais
});
L'événement «Thing1» ci-dessus ne sera jamais capturé par mything (), car mything () doit être instancié avant qu'il ne puisse écouter les événements. Voici une solution de contournement simple sans avoir à ajouter de fermetures supplémentaires:
La copie de code est la suivante:
var eventEMmitter = require ('Events');
var util = require ('util');
fonction mything () {
EventEmitter.Call (this);
Dofirstthing ();
SEMEMMEDIAD (IMITTHAGE1, This);
}
util.inherits (mything, eventEmitter);
fonction émettant1 (self) {
self.emit ('thing1');
}
var mt = new Mything ();
Mt.on ('Thing1', fonction Onthing1 () {
// Exécuter
});
Le schéma suivant peut également fonctionner, mais certaines performances sont perdues:
La copie de code est la suivante:
fonction mything () {
EventEmitter.Call (this);
Dofirstthing ();
// Utilisation de la fonction # bind () perdra des performances
SetImMediate (this.emit.bind (this, 'thing1'));
}
util.inherits (mything, eventEmitter);
Un autre problème consiste à déclencher une erreur (exception). Il est déjà difficile de découvrir le problème dans votre application, mais sans la pile d'appels (note * e.stack), le débogage est presque impossible. La pile d'appels sera perdue lorsque l'erreur sera demandée par la distance de manière asynchrone. Il existe deux solutions possibles: le déclenchement synchrone ou s'assurer que l'erreur est transmise avec d'autres informations importantes. L'exemple suivant montre ces deux solutions:
La copie de code est la suivante:
Mything.prototype.foo = fonction foo () {
// Cette erreur sera déclenchée de manière asynchrone
var er = dofirstthing ();
if (er) {
// Lors du déclenchement, vous devez créer une nouvelle erreur qui conserve les informations sur la pile d'appels sur place.
SetImMediate (Emiterror, this, nouvelle erreur («mauvais trucs»));
retour;
}
// déclencher l'erreur et le traiter immédiatement (synchroniser)
var er = doseConDthing ();
if (er) {
this.emit ('error', 'Plus de mauvais trucs');
retour;
}
}
Évaluer la situation. Lorsqu'une erreur est déclenchée, il est possible d'être traité immédiatement. Alternativement, il peut être trivial et peut être facilement manipulé ou manipulé plus tard. De plus, passer une erreur par un constructeur n'est pas une bonne idée, car l'instance d'objet construit est probablement incomplète. L'exception est le cas où l'erreur a été directement lancée à l'heure.
Conclusion
Cet article explore brièvement le mécanisme de fonctionnement interne et les détails techniques de la boucle d'événement. Tous sont soigneusement pris en considération. Un autre article discutera de l'interaction entre les boucles d'événements et les grains du système et démontrera la magie des Nodejs fonctionnant de manière asynchrone.