El "bucle de eventos" de Node es el núcleo de su capacidad para manejar una gran concurrencia y alto rendimiento. Este es el lugar más mágico. Según esto, Node.js se puede entender básicamente como "un solo subproceso", y también permite que las operaciones arbitrarias se procesen en segundo plano. Este artículo ilustrará cómo funcionan los bucles de eventos y usted también puede sentir su magia.
Programación impulsada por eventos
Para comprender los bucles de eventos, primero debemos comprender la programación de la unidad de eventos. Apareció en 1960. Hoy, la programación basada en eventos se usa ampliamente en la programación de la interfaz de usuario. Uno de los principales usos de JavaScript es interactuar con el DOM, por lo que es natural usar una API basada en eventos.
Simplemente defina: la programación basada en eventos controla el proceso de aplicación a través de cambios en eventos o estados. Generalmente se implementa a través del monitoreo de eventos. Una vez que se detecta el evento (es decir, el estado cambia), se llama a la función de devolución de llamada correspondiente. ¿Suena familiar? De hecho, este es el principio de trabajo básico del bucle de eventos Node.js.
Si está familiarizado con el desarrollo de JavaScript del lado del cliente, piense en esos métodos .on*(), como element.OnClick (), que se utilizan para combinar con elementos DOM para aprobar la interacción del usuario. Este modo de trabajo permite que se activen múltiples eventos en una sola instancia. Node.js desencadena este modo a través de EventEmitter (generador de eventos), como en los módulos de socket y "http" en el lado del servidor. Uno o más cambios en el estado pueden activarse desde una sola instancia.
Otro patrón común es expresar éxito y fracaso. Generalmente hay dos métodos de implementación comunes ahora. Lo primero es pasar la "excepción de error" a la devolución de llamada, que generalmente se pasa a la función de devolución de llamada como el primer parámetro. El segundo tipo es usar el modo de diseño de promesas, y se ha agregado ES6. Nota* El modo prometedor utiliza un método de escritura de cadena de funciones tipo jQuery para evitar la anidación de la función de devolución de llamada profunda, como:
La copia del código es la siguiente:
$ .getjson ('/getUser'). Do (SuccessHandler) .Fail (FailHandler)
Los módulos "FS" (sistema de archivos) utilizan principalmente el estilo de pasar excepciones en la devolución de llamada. Técnicamente, activando ciertas llamadas, como el evento adjunto Fs.ReadFile (), pero la API solo se usa para recordarle al usuario que exprese el éxito o la falla de la operación. Tal API se elige por razones arquitectónicas, no de limitaciones técnicas.
Una idea errónea común es que los emisores de eventos también son inherentemente asíncronos cuando se desencadenan eventos, pero esto es incorrecto. Aquí hay un fragmento de código simple para probar esto.
La copia del código es la siguiente:
function myEmitter () {
EventEmitter.call (esto);
}
Util.inherits (Myemitter, EventEmitter);
Myemitter.prototype.dostuff = function dostuff () {
console.log ('antes')
emitter.emit ('Fuego')
console.log ('después')}
};
var me = new MyEmitter ();
me.on ('fire', function () {
console.log ('emit disparado');
});
me.dostuff ();
// Producción:
// antes
// emitir disparado
// después
Nota* Si emitter.emit es asíncrono, la salida debe ser
// antes
// después
// emitir disparado
EventEmister a menudo se manifiesta asincrónicamente porque a menudo se usa para notificar las operaciones que deben completarse de manera asincrónica, pero la API EventEmitter en sí es totalmente sincrónica. La función de escucha se puede ejecutar de forma asincrónica, pero tenga en cuenta que todas las funciones de escucha se ejecutarán sincrónicamente en el orden en que se agregan.
Descripción general del mecanismo y agrupación de hilos
El nodo en sí se basa en múltiples bibliotecas. Uno de ellos es Libuv, una biblioteca que maneja mágicamente las colas y ejecuciones de eventos asincrónicos.
Node utiliza tantas funciones existentes como sea posible para utilizar el kernel del sistema operativo. Por ejemplo, se genera una solicitud de respuesta, las conexiones se reenvían y delegan al sistema para su procesamiento. Por ejemplo, las conexiones entrantes se ponen en cola a través del sistema operativo hasta que puedan procesarse por nodo.
Es posible que haya escuchado que el nodo tiene un grupo de subprocesos, y puede preguntarse: "Si el nodo maneja las tareas en orden, ¿por qué necesita un grupo de hilos?" Esto se debe a que en el núcleo, no todas las tareas se ejecutan asincrónicamente. En este caso, Node.js debe poder bloquear el hilo durante un período de tiempo mientras funciona para que pueda continuar ejecutando el bucle de eventos sin ser bloqueado.
El siguiente es un diagrama de ejemplo simple para mostrar su mecanismo operativo interno:
"
──►│ Timesers │
│ └─── razón
│ ┌─── razón
│ │ Pendientes de devoluciones de llamada │
│ └─── razón
│ ┌─── razón
│ │ │ encuesta │◄──┤ conexiones, │
│ └─── razón
│ ┌─── razón
──┤ setimmediate │
"
Hay algunas dificultades para comprender el mecanismo de operación interna del bucle de eventos:
Todas las devoluciones de llamada están preestablecidas a través de Process.NextTick () antes del final de una fase del bucle de eventos (por ejemplo, un temporizador) y la transición a la siguiente fase. Esto evitará la posible llamada recursiva para procesar.nextTick (), causando un bucle infinito.
"Pendientes de devolución de llamada" es una devolución de llamada en la cola de devolución de llamada que no será procesada por ningún otro ciclo de bucle de eventos (por ejemplo, pasar a Fs.Write).
Emisor de eventos y bucle de eventos
Al crear EventEmitter, la interacción con los bucles de eventos se puede simplificar. Es una encapsulación universal que le facilita crear API basadas en eventos. Cómo interactúan los dos a menudo hace que los desarrolladores se sientan confundidos.
El siguiente ejemplo muestra que olvidar que el evento se activa sincrónicamente puede hacer que se pierda el evento.
La copia del código es la siguiente:
// Después de V0.10, requerir ('eventos'). EventEmitter ya no es necesario
var eventEmitter = request ('eventos');
var util = require ('util');
function mything () {
EventEmitter.call (esto);
dofirstthing ();
this.emit ('Thing1');
}
Util.inherits (Mything, EventEmitter);
var mt = new mything ();
mt.on ('thing1', function onthing1 () {
// Lo siento, este incidente nunca sucederá
});
El evento 'Thing1' anterior nunca será atrapado por mything (), porque mything () debe ser instanciado antes de que pueda escuchar los eventos. Aquí hay una solución simple sin tener que agregar ningún cierre adicional:
La copia del código es la siguiente:
var eventEmitter = request ('eventos');
var util = require ('util');
function mything () {
EventEmitter.call (esto);
dofirstthing ();
setimmediate (emithing1, esto);
}
Util.inherits (Mything, EventEmitter);
Función EMITTHING1 (Self) {
self.emit ('Thing1');
}
var mt = new mything ();
mt.on ('thing1', function onthing1 () {
// Ejecutar
});
El siguiente esquema también puede funcionar, pero se pierde algo de rendimiento:
La copia del código es la siguiente:
function mything () {
EventEmitter.call (esto);
dofirstthing ();
// Uso de la función#bind () perderá el rendimiento
setimmediate (this.emit.bind (this, 'Thing1'));
}
Util.inherits (Mything, EventEmitter);
Otro problema es activar un error (excepción). Ya es difícil descubrir el problema en su aplicación, pero sin la pila de llamadas (nota *e.stack), la depuración es casi imposible. La pila de llamadas se perderá cuando el error sea solicitado por el remoto asincrónico. Hay dos posibles soluciones: activación sincrónica o garantizar que se pase el error con otra información importante. El siguiente ejemplo demuestra estas dos soluciones:
La copia del código es la siguiente:
Mything.prototype.foo = function foo () {
// Este error se activará asincrónicamente
var er = dofirstthing ();
if (er) {
// Al activar, debe crear un nuevo error que conserve la información de la pila de llamadas en el sitio.
setimmediate (emiterror, este nuevo error ('cosas malas'));
devolver;
}
// activar el error y procesarlo inmediatamente (sincronizar)
var er = doseCondthing ();
if (er) {
this.emit ('error', 'más cosas malas');
devolver;
}
}
Evaluar la situación. Cuando se activa un error, es posible procesarse de inmediato. Alternativamente, puede ser trivial y puede manejarse fácilmente o manejarse más tarde. Además, pasar un error a través de un constructor no es una buena idea, porque es probable que la instancia del objeto construido esté incompleta. La excepción es el caso en el que el error se lanzó directamente ahora.
Conclusión
Este artículo explora brevemente el mecanismo operativo interno y los detalles técnicos del bucle de eventos. Todos se consideran cuidadosamente. Otro artículo discutirá la interacción entre los bucles de eventos y los núcleos del sistema y demostrará la magia de los nodejs que se ejecutan asincrónicamente.