bucle de eventos en el nodo js

Bucle De Eventos En El Nodo Js



Node.js es un potente marco de Javascript que permite a los usuarios ejecutar código Javascript en el servidor fuera del navegador. Es un entorno de ejecución sin bloqueos y controlado por eventos para crear aplicaciones web escalables y confiables. El bucle de eventos es una parte importante de Node.js que le permite realizar tareas sin esperar a que una termine antes de comenzar otra.

Aunque Javascript es un lenguaje de subproceso único, Node.js puede asignar tareas al sistema operativo, permitiéndole procesar múltiples tareas al mismo tiempo. Se deben completar varias tareas al mismo tiempo porque las operaciones en el sistema operativo son multiproceso. La devolución de llamada asociada con cada operación se agrega a la cola de eventos y Node.js la programa para que se ejecute cuando se complete la tarea especificada.

Para escribir código Node.js eficiente y confiable, el usuario debe tener un conocimiento sólido de los bucles de eventos. También puede ayudar a solucionar problemas de rendimiento de forma eficaz. El bucle de eventos en Node.js ahorra memoria y le permite hacer varias cosas a la vez sin tener que esperar a que termine cada una. El término 'asíncrono' se refiere a cualquier función de Javascript que se ejecuta en segundo plano sin bloquear las solicitudes entrantes.







Antes de pasar directamente a los bucles de eventos, echemos un vistazo a diferentes aspectos del lenguaje de programación Javascript.



Javascript como lenguaje de programación asíncrono

Echemos un vistazo a los conceptos de programación asincrónica. Javascript se utiliza en aplicaciones web, móviles y de escritorio, pero cabe señalar que Javascript es un lenguaje de programación informática síncrono y de un solo subproceso.



Se proporciona un ejemplo de código simple para comprender el concepto.





método de función1 ( ) {

consola. registro ( 'Función 1' )

}

método de función2 ( ) {

consola. registro ( 'Función 2' )

}

Método 1 ( )

método2 ( )

En este código, se crean dos funciones simples y se llama primero al método1 para que registre el método1 primero y luego pase al siguiente.

Producción



Javascript como lenguaje de programación sincrónico

Javascript es un lenguaje de programación sincrónico y ejecuta cada línea paso a paso, moviéndose de arriba a abajo con solo una línea ejecutada a la vez. En el código de ejemplo proporcionado anteriormente, el método1 se registra primero en la terminal y luego en el método2.

Javascript como lenguaje de bloqueo

Al ser un lenguaje síncrono, JavaScript tiene una funcionalidad de bloqueo. No importa cuánto tiempo lleve completar un proceso en curso, pero no se iniciará un nuevo proceso hasta que se haya completado el anterior. En el ejemplo de código anterior, supongamos que hay una gran cantidad de secuencias de comandos de código en el método1, sin importar cuánto tiempo tarde, ya sea 10 segundos o un minuto, el método2 no se ejecutará hasta que se haya ejecutado todo el código del método1.

Es posible que los usuarios hayan experimentado esto mientras navegaban. Cuando una aplicación web se ejecuta en un navegador en el back-end, se ejecuta una gran cantidad de código, por lo que el navegador parece estar congelado durante algún tiempo antes de devolver el acceso de control al usuario. Este comportamiento se conoce como bloqueo. El navegador no puede aceptar más solicitudes entrantes hasta que se haya procesado la solicitud actual.

Javascript es un lenguaje de un solo subproceso

Para ejecutar un programa en javascript se utiliza la funcionalidad de hilo. Los subprocesos sólo son capaces de realizar una tarea a la vez. Otros lenguajes de programación admiten subprocesos múltiples y pueden ejecutar múltiples tareas en paralelo, javascript contiene solo un subproceso para ejecutar cualquier script de código.

Esperando en Javascript

Como se desprende del nombre de esta sección, tenemos que esperar a que se procese nuestra solicitud para continuar. La espera puede durar varios minutos durante los cuales no se atiende a más solicitudes. Si el script del código continúa sin esperar, el código encontrará un error. Algunas funciones se implementarán en Javascript o, más específicamente, en Node.js para que el código sea asincrónico.

Ahora que hemos entendido los diferentes aspectos de Javascript, comprendamos lo sincrónico y asincrónico con algunos ejemplos simples.

Ejecución sincrónica de código en Javascript

Sincrónico significa que el código se ejecuta secuencialmente o, más simplemente, paso a paso, comenzando desde arriba y bajando línea por línea.

A continuación se proporciona un ejemplo que puede ayudar a comprender:

// aplicación.js

consola. registro ( 'Uno' )

consola. registro ( 'Dos' )

consola. registro ( 'Tres' )

En este código, hay tres declaraciones console.log, cada una de las cuales imprime algo. En primer lugar, la primera declaración que imprimirá 'Uno' en la consola se envía a la pila de llamadas durante 1 ms (estimado) y luego se registra en el terminal. Después de eso, la segunda declaración se inserta en la pila de llamadas y ahora el tiempo es de 2 ms con una agregada de la anterior y luego registra 'Dos' en la consola. Finalmente, la última declaración se inserta en la pila de llamadas; ahora el tiempo es de 3 ms y registra 'Tres' en la consola.

El código anterior se puede ejecutar invocando el siguiente comando:

aplicación de nodo. js

Producción

El funcionamiento se explica anteriormente en detalle y, al tenerlo en cuenta, la salida se registra en la consola en un abrir y cerrar de ojos:

Ejecución asincrónica de código en Javascript

Ahora refactoricemos el mismo código introduciendo devoluciones de llamada y haciendo que el código sea asincrónico. El código anterior se puede refactorizar como:

// aplicación.js
función printOne ( llamar de vuelta ) {
establecer tiempo de espera ( función ( ) {
consola. registro ( 'Uno' ) ;
llamar de vuelta ( ) ;
    } , 1000 ) ;
}
función imprimir dos ( llamar de vuelta ) {
establecer tiempo de espera ( función ( ) {
consola. registro ( 'Dos' ) ;
llamar de vuelta ( ) ;
    } , 2000 ) ;
}
función imprimir tres ( ) {
establecer tiempo de espera ( función ( ) {
consola. registro ( 'Tres' ) ;
    } , 3000 ) ;
}
consola. registro ( 'Inicio del programa' ) ;
imprimiruno ( función ( ) {
imprimir dos ( función ( ) {
imprimirtres ( ) ;
    } ) ;
} ) ;
consola. registro ( 'Fin del programa' ) ;

En este código de arriba:

  • Se declaran tres funciones para imprimir 'Uno', 'Dos' y 'Tres', cada función tiene un parámetro de devolución de llamada que permite la ejecución secuencial de código.
  • Se establece un tiempo de espera mediante la función setTimeout y hay una declaración console.log para imprimir después de un retraso específico.
  • Se imprimen dos mensajes “Inicio del Programa” y “Fin del Programa” que indican el inicio y el final del programa.
  • El programa comienza imprimiendo “Inicio del programa” después de lo cual se ejecuta la función printOne con un retraso de 1 segundo, luego la función printTwo se ejecuta con un retraso de 2 segundos y finalmente la función printThree se ejecuta con un retraso de 3 segundos. demora.
  • El programa no espera las ejecuciones de código asincrónico dentro de las funciones setTimeouts que registran la declaración de 'Fin del programa' antes de imprimir Uno, Dos y Tres.

Producción

Ejecute el código anterior ejecutando este comando en la terminal:

aplicación de nodo. js

Ahora la salida en la terminal se mostraría de forma asincrónica como:

Ahora que tenemos una comprensión completa de la ejecución sincrónica y asincrónica, pasemos a solidificar nuestro concepto de bucle de eventos en Node.js.

Node.js: mecanismo de bucle de eventos

La ejecución de tareas sincrónicas y asincrónicas se gestiona mediante el bucle de eventos en Node.js. La ejecución se invoca tan pronto como se inicia el proyecto Node.js y transfiere sin problemas las tareas complejas al sistema. Esto garantiza que otras tareas puedan ejecutarse sin problemas en el hilo principal.

Explicación visual del bucle de eventos en Node.js

El bucle de eventos es continuo y semiinfinito en Node.js. El bucle de eventos se invoca al inicio del script de código de Node.js y es responsable de realizar llamadas API asíncronas y llamar a Process.Tick(), y programar temporizadores para luego reanudar la ejecución del bucle de eventos.

En Node.js, cinco tipos principales de colas manejan devoluciones de llamadas:

  • La 'cola de temporizador', comúnmente conocida como montón mínimo, es responsable de manejar las devoluciones de llamada asociadas con 'setTimeout' y 'setInterval'.
  • Las devoluciones de llamada para operaciones asincrónicas como en los módulos 'fs' y 'http' son manejadas por la 'Cola de E/S'.
  • La 'Cola de verificación' contiene devoluciones de llamada para la función 'setImmediate' que es exclusiva de Node.
  • La 'Cola de cierre' gestiona las devoluciones de llamadas asociadas con el evento de cierre de cualquier tarea asincrónica.
  • Por último, hay dos colas diferentes en la cola 'Micro Tarea':
    • La cola 'nextTick' contiene devoluciones de llamada asociadas con la función 'process.nextTick'.
    • La cola 'Promise' controla las devoluciones de llamadas relacionadas con la promesa nativa.

Funcionalidad de bucle de eventos en Node.js

El bucle de eventos funciona bajo requisitos específicos que controlan el orden de ejecución de la devolución de llamada. El código Javascript síncrono del usuario tiene prioridad al inicio del proceso, por lo que el bucle de eventos solo comienza cuando se borra la pila de llamadas. La siguiente secuencia de ejecución sigue un patrón estructurado:

La prioridad más alta se otorga a las devoluciones de llamada en la cola de microtareas y luego se mueven para ejecutar las tareas en la cola nextTick seguidas de las tareas en la cola Promise. Luego, los procesos en las devoluciones de llamada de la cola del temporizador se manejan y luego se vuelve a visitar la cola de microtareas después de cada devolución de llamada del temporizador. Las devoluciones de llamada en las colas de E/S, verificación y cierre se ejecutan en un patrón similar con la cola de microtareas visitada después de cada fase.

El bucle continúa ejecutándose si hay más devoluciones de llamada para procesar. Cuando el script del código finaliza o no quedan devoluciones de llamada para procesar, el bucle de eventos finaliza de manera eficiente.

Ahora que entendemos profundamente el bucle de eventos, veamos sus características.

Características del bucle de eventos en Node.js

Las características principales son:

  • El bucle de eventos es un bucle infinito y continúa ejecutando las tareas tan pronto como las recibe y entra en modo de suspensión en caso de que no haya tareas, pero comienza a funcionar tan pronto como se recibe la tarea.
  • Las tareas en la cola de eventos se ejecutan solo cuando la pila está vacía, lo que significa que no hay ninguna operación activa.
  • Se pueden utilizar devoluciones de llamada y promesas en el bucle de eventos.
  • Como el bucle de eventos sigue el principio de la cola de tipo de datos abstracto, cumple la primera tarea y luego pasa a la siguiente.

Después de una comprensión profunda del bucle de eventos y la lógica de las ejecuciones asíncronas y sincrónicas, comprender las diferentes fases puede solidificar los conceptos del bucle de eventos.

Fases del bucle de eventos de Node.js

Como se mencionó anteriormente, el bucle de eventos es semiinfinito. Tiene muchas fases pero algunas fases se utilizan para manejo interno. Estas fases no tienen ningún efecto en el script del código.

El bucle de eventos sigue la funcionalidad de la cola y ejecuta la tarea según el principio de primero en entrar y primero en salir. Los temporizadores programados serán manejados por el sistema operativo hasta que expiren. Luego, los temporizadores expirados se agregan a la cola de devolución de llamada de temporizadores.

El bucle de eventos ejecuta las tareas en la cola del temporizador una por una hasta que no quedan más tareas o se alcanza el número máximo permitido de tareas. En las secciones siguientes se explican las fases principales de los bucles de eventos.

Fase de temporizadores

En Node.js hay una API de temporizador que puede programar las funciones que se ejecutarán en el futuro. Una vez transcurrido el tiempo asignado, la devolución de llamada del temporizador se ejecutará tan pronto como se pueda programar; sin embargo, es posible que se produzca un retraso desde el extremo del sistema operativo o debido a la ejecución de otras devoluciones de llamada.

La API de temporizadores tiene tres funciones principales:

  • establecer tiempo de espera
  • establecerInmediato
  • establecer intervalo

Las funciones mencionadas anteriormente son síncronas. La fase del temporizador en el bucle de eventos tiene su alcance limitado a las funciones setTimeout y setInterval. Mientras que la función de verificación maneja la función setImmediate.

Consideremos un ejemplo simple para solidificar la parte teórica:

// aplicación.js

función retardadaFunción ( ) {

consola. registro ( 'la función retrasada se ejecuta después del tiempo de espera' ) ;

}

consola. registro ( 'Inicio del programa' ) ;

establecer tiempo de espera ( función retrasada, 2000 ) ;

consola. registro ( 'Fin del programa' ) ;

En este código:

  • El programa comienza registrando la declaración 'Inicio del programa' en la terminal.
  • Luego se llama a la función retrasada con un temporizador de 2 ms, el script del código no se detiene y continúa manejando el retraso en segundo plano.
  • La declaración “Fin del programa se registra después de la primera declaración.
  • Después de un retraso de 2 ms, la declaración en la función retrasada se registra en el terminal.

Producción

El resultado se mostrará como:

Se puede ver que el código no se detiene para que lo procese la función retrasada; avanza y después del retraso, se procesa la devolución de llamada de la función.

Devoluciones de llamadas pendientes

El bucle de eventos verifica los eventos que suceden, como la lectura de archivos, actividades de red o tareas de entrada/salida, en la fase de sondeo. Es importante saber que, en Node.js, sólo algunos de los eventos se manejan en esta fase de sondeo. Sin embargo, en la iteración posterior del bucle de eventos, ciertos eventos pueden diferirse a la fase pendiente. Este es un concepto clave a tener en cuenta al optimizar y solucionar problemas del código Node.js que involucra operaciones complejas basadas en eventos.

Es importante comprender que durante la fase de devoluciones de llamada en espera, el bucle de eventos agrega eventos pospuestos a la cola de devoluciones de llamada pendientes y los realiza. Esta fase también maneja algunos errores de socket TCP que ha generado el sistema, como eventos de error ECONNREFUSED en ciertos sistemas operativos.

A continuación se menciona un ejemplo para solidificar el concepto:

// aplicación.js
constante fs = requerir ( 'fs' ) ;
función leerFileAsync ( ruta de archivo, devolución de llamada ) {
fs. leer archivo ( './PromiseText.txt' , 'utf8' , función ( errar, datos ) {
    si ( errar ) {
consola. error ( ` Error leyendo archivo : $ { errar. mensaje } ` ) ;
    } demás {
consola. registro ( ` Archivo contenido : $ { datos } ` ) ;
    }
llamar de vuelta ( ) ;
  } ) ;
}
consola. registro ( 'Inicio del programa' ) ;
leerFileAsync ( './PromiseText.txt' , función ( ) {
consola. registro ( 'Devolución de llamada de lectura de archivo ejecutada' ) ;
} ) ;
consola. registro ( 'Fin del programa' ) ;

En este código:

  • El programa se inicia registrando la declaración 'Inicio del programa' en la terminal.
  • readFileAsync se define de forma asincrónica para leer el contenido del archivo 'PromiseText.txt'. Es una función parametrizada que ejecuta una función de devolución de llamada después de que se ha leído el archivo.
  • Se llama a la función readFileAsync para comenzar el proceso de lectura de archivos.
  • En el proceso de lectura del archivo, el programa no se detiene; en su lugar, pasa a la siguiente declaración y la registra en la terminal 'Fin del programa'.
  • El evento asincrónico de lectura de archivos se procesa en segundo plano mediante el bucle de eventos.
  • Una vez que el archivo se ha leído de forma asíncrona y el contenido se ha registrado en el terminal, el programa registra el contenido del archivo en el terminal. Después de eso, registra el siguiente mensaje 'Devolución de llamada de lectura de archivo ejecutada'.
  • El bucle de eventos maneja las operaciones de devolución de llamada pendientes en la siguiente fase.

Producción

El resultado de la ejecución anterior es:

Inactivo, fase de preparación en Node.js

La fase inactiva se utiliza para gestionar funciones internas en Node.js, por lo que no es una fase estándar. No influye en el script del código. La fase inactiva es como un período de pausa para el bucle de eventos durante el cual se gestionan las tareas de baja prioridad en segundo plano. Un ejemplo sencillo para entender esta fase es:

constante { inactivo } = requerir ( 'inactivo-gc' ) ;

inactivo. ignorar ( ) ;

En este código se utiliza el módulo “idle-gc” que permite ignorar la fase inactiva. Esto sirve para manejar situaciones en las que el bucle de eventos está ocupado y no se realizan tareas en segundo plano. El uso de idle.ignore no se considera óptimo ya que puede provocar problemas de rendimiento.

Fase de sondeo en Node.js

La fase de encuesta en Node.js sirve como:

  • Maneja los eventos en la cola de sondeo y realiza sus tareas correspondientes.
  • Decide cuánto tiempo dedicar a esperar y comprobar las operaciones de E/S en el proceso.

A medida que el bucle de eventos ingresa a la fase de sondeo debido a la ausencia de un temporizador, se realizará una de las siguientes tareas:

  • En la fase de sondeo del bucle de eventos en Node.js, los eventos de E/S pendientes se ponen en cola y luego se ejecutan en un procedimiento secuencial siguiendo el principio de Primero en entrar y Primero en salir hasta que la cola queda vacía. Durante las ejecuciones de devoluciones de llamada, las colas nextTick y microtasks también están en acción. Esto garantiza la fluidez y permite manejar las operaciones de E/S de manera más eficiente y confiable.
  • Si la cola está vacía y el script no ha sido programado por la función setImmediate(), entonces el bucle de eventos finalizará y pasará a la siguiente fase (verificación). Por otro lado, si la programación del script se realizó mediante la función setImmediate(), el bucle de eventos permite que las devoluciones de llamada se agreguen a la cola que ejecutará.

Esto se ilustra mejor con un ejemplo de código simple:

establecer tiempo de espera ( ( ) => {

consola. registro ( 'Operación asíncrona completada' ) ;

} , 2000 ) ;

consola. registro ( 'Comenzar' ) ;

establecerInmediato ( ( ) => {

consola. registro ( 'Devolución de llamada setImmediate ejecutada' ) ;

} ) ;

consola. registro ( 'Fin' ) ;

En este código:

  • Dos mensajes “Inicio” y “Fin” indican el inicio y la finalización del programa.
  • La función setTimeout() establece una función de devolución de llamada con un retraso de 2 ms y registra la “operación asíncrona completada” en el terminal.
  • La función setImmediate() registra el mensaje “devolución de llamada setImmediate ejecutada” en el terminal después de que el mensaje de inicio se haya registrado en el terminal.

Producción

El resultado mostraría los mensajes con solo una observación mínima de que la 'operación asíncrona completada' lleva tiempo y se imprime después del mensaje 'Fin':

Fase de verificación de Node.js

Una vez ejecutada la fase de sondeo, se ejecutan las devoluciones de llamada en la fase de verificación. Si se programa un script de código usando la función setImmediate() y la función de sondeo es gratuita, el bucle de eventos funciona moviéndose directamente a la fase de verificación en lugar de permanecer inactivo. La función setImmediate() es un temporizador único que opera durante las diferentes fases del bucle de eventos.

La API libuv se utiliza para planificar las ejecuciones de devolución de llamada una vez completada la ejecución de la fase de encuesta. Durante la ejecución del código, el bucle de eventos ingresa a la fase de sondeo en la que espera las solicitudes de conexión entrantes. En otro caso, si la devolución de llamada se programa utilizando la función setImmediate() y la fase de sondeo finaliza sin ninguna actividad, pasará a la fase de verificación en lugar de esperar. Considere el siguiente ejemplo para comprenderlo:

// aplicación.js

consola. registro ( 'Comenzar' ) ;

establecerInmediato ( ( ) => {

consola. registro ( 'Devolución de llamada inmediata' ) ;

} ) ;

consola. registro ( 'Fin' ) ;

En este código se registran tres mensajes en el terminal. La función setImmediate() finalmente envía una devolución de llamada para registrar el mensaje ' Devolución de llamada inmediata ”a la terminal.

Producción

El resultado del código anterior aparecerá en la siguiente secuencia:

Node.js cierra devoluciones de llamada

Node.js usa esta fase de cierre para ejecutar devoluciones de llamada para cerrar eventos y finalizar una iteración del bucle de eventos. Una vez cerrada la conexión, el bucle de eventos maneja los eventos de cierre en esta fase. En esta fase del bucle de eventos, 'nextTick()' y microtareas se generan y procesan de manera similar a otras fases.

La función Process.exit se utiliza para finalizar el bucle de eventos en cualquier instante. El bucle de eventos ignorará cualquier operación asincrónica pendiente y el proceso de Node.js finalizará.

Un ejemplo sencillo a considerar es:

// aplicación.js
constante neto = requerir ( 'neto' ) ;
constante servidor = neto. crearservidor ( ( enchufe ) => {
enchufe. en ( 'cerca' , ( ) => {
consola. registro ( 'Toma cerrada' ) ;
  } ) ;
enchufe. en ( 'datos' , ( datos ) => {
consola. registro ( 'Datos recibidos:' , datos. Encadenar ( ) ) ;
  } ) ;
} ) ;
servidor. en ( 'cerca' , ( ) => {
consola. registro ( 'Servidor cerrado' ) ;
} ) ;
constante puerto = 3000 ;
servidor. escuchar ( puerto, ( ) => {
consola. registro ( `Servidor escuchando en el puerto $ { puerto } ` ) ;
} ) ;
establecer tiempo de espera ( ( ) => {
consola. registro ( 'Cerrando el servidor después de 10 segundos' ) ;
servidor. cerca ( ) ;
proceso. salida ( ) ;
} , 10000 ) ;

En este código:

  • constante neto = requerir('neto') 'importa el módulo de red necesario para manejar un servidor TCP y' servidor constante = net.createServer((socket) => { ”crea una nueva instancia de servidor TCP.
  • socket.on('cerrar', () => {... } ” escucha el “cierre” en todos los enchufes. Cuando se cierra la conexión del socket, se registra el mensaje 'Socket cerrado' en el terminal.
  • socket.on('datos', (datos) => {} ”comprueba los datos entrantes de todos los sockets individuales y los imprime usando la función “.toString()”.
  • servidor.on('cerrar', () => {...} 'Comprueba el evento de 'cierre' en el propio servidor y, cuando se cierra la conexión del servidor, registra el mensaje 'Servidor cerrado' en el terminal.
  • servidor.listen(puerto, () => {…} 'Escucha las conexiones entrantes en el puerto.
  • setTimeout(() => {…} ” establece un temporizador de 10 ms para cerrar el servidor.

Con esto concluye la discusión sobre las distintas fases del bucle de eventos en Node.js. Antes de llegar a una conclusión, analicemos una última cosa: cómo salir del bucle de eventos en Node.js.

Salir del bucle de eventos en Node.js

El bucle de eventos está en la fase de ejecución siempre que haya algunas tareas en todas las colas de las fases del bucle de eventos. El bucle de eventos finaliza después de que se emite la fase de salida y la devolución de llamada del detector de salida regresa si no hay más tareas en las colas.

La forma explícita de finalizar un bucle de eventos es utilizar el método '.exit'. Los procesos activos de Node.js finalizarán instantáneamente tan pronto como se llame a la función Process.exit. Se eliminarán todos los eventos programados y pendientes:

proceso. en ( 'salida' , ( código ) => {

consola. registro ( `Saliendo con código de salida : $ { código } ` ) ;

} ) ;

proceso. salida ( 1 ) ;

Los usuarios pueden escuchar la función .exit. Cabe señalar que la función '.exit' debe ser sincrónica ya que el programa Node.js se cerrará tan pronto como escuche este evento.

Con esto concluye la discusión sobre el bucle de eventos. Un artículo detallado que ha cubierto todos los conceptos, fases y ejemplos relacionados con el bucle de eventos.

Conclusión

Antes de comprender el bucle de eventos, una descripción general de los conceptos sincrónicos y asincrónicos puede ayudar a comprender el flujo de código en el bucle de eventos. La ejecución sincrónica significa una ejecución paso a paso, mientras que la ejecución asincrónica significa detener algunos pasos sin esperar a que se completen. En el artículo se analiza el funcionamiento del bucle de eventos junto con todas las fases y ejemplos adecuados.