Bucle de eventos de Node.js

El bucle de eventos es el mecanismo central de Node.js para manejar operaciones de E/S no bloqueantes, lo que permite que un solo hilo maneje eficientemente múltiples solicitudes concurrentes.

Node.js es un entorno de ejecución de JavaScript basado en un solo hilo que utiliza el bucle de eventos para manejar operaciones asíncronas, como lectura de archivos, solicitudes de red y consultas de bases de datos.

El bucle de eventos permite que Node.js ejecute código de forma no bloqueante, maneje múltiples conexiones y realice operaciones de E/S asíncronas.

El bucle de eventos permite que Node.js maneje una gran cantidad de operaciones de E/S concurrentes sin causar bloqueo de subprocesos; esta es la clave para que Node.js procese solicitudes concurrentes de manera eficiente.

Fases del bucle de eventos

El bucle de eventos se divide en varias fases, cada una maneja tareas específicas. Las fases clave son las siguientes:

  • Timerstimers: ejecutasetTimeout()ysetInterval()las devoluciones de llamada de setTimeout y setInterval.
  • I/O Callbackspending callbacks: maneja algunas devoluciones de llamada de E/S diferidas.
  • Idle, prepareidle, prepare: uso interno, poco común.
  • Pollpoll: recupera nuevos eventos de E/S y ejecuta las devoluciones de llamada relacionadas con E/S.
  • Checkcheck: ejecutasetImmediate()la devolución de llamada de setImmediate().
  • Close Callbacksclose callbacks: maneja las devoluciones de llamada de cierre, como socket.on('close', ...).socket.on('close', ...)。

Flujo del bucle de eventos

  • Las tareas entran en la cola del bucle de eventos。
  • El bucle de eventos procesa en orden de fases, y cada fase tiene su propia cola de devoluciones de llamada.
  • El bucle de eventos estarápollen la fase poll esperando la llegada de nuevos eventos; si no hay eventos, revisará las devoluciones de llamada de otras fases.
  • SisetImmediate()ysetTimeout()ambos existen,setImmediate()Encheckla fase poll se ejecuta primero, mientras quesetTimeout()Entimersla fase check se ejecuta.

Ejemplo

setTimeout(() => {
  console.log('Timeout callback');
}, 0);

setImmediate(() => {
  console.log('Immediate callback');
});

console.log('Main thread execution');

Orden de salida:

  • Main thread executionsetTimeout se imprime primero.
  • setImmediate()ysetTimeout()El orden de ejecución de setImmediate depende del estado actual del bucle de eventos; generalmentesetImmediate()setImmediate se ejecutará primero.

Macrotareas y microtareas

  • Macrotareas::setTimeout、setInterval、setImmediatesetTimeout, setInterval, operaciones de E/S, etc.
  • Microtareas: Promise, process.nextTick, etc.:process.nextTick、Promise.then。

Orden de ejecución:Las microtareas tienen mayor prioridad que las macrotareas y se ejecutan inmediatamente después de que finalicen las devoluciones de llamada de la fase actual.

Ejemplo

setTimeout(() => {
  console.log('Timeout callback');
}, 0);

Promise.resolve().then(() => {
  console.log('Promise callback');
});

console.log('Main thread execution');

Resultado de la ejecución:

Main thread execution
Promise callback
Timeout callback

process.nextTick()

process.nextTick() ejecutará microtareas después de que finalice la operación actual y antes de que comience la siguiente fase, con mayor prioridad que Promise.

Ejemplo

process.nextTick(() => {
  console.log('Next tick callback');
});

console.log('Main thread execution');

Salida:

Main thread execution
Next tick callback

Programación controlada por eventos

En Node.js, la programación controlada por eventos se implementa principalmente mediante la clase EventEmitter.

EventEmitter es una clase integrada en el módulo events. Al heredar de EventEmitter, puedes crear tu propio emisor de eventos, así como registrar y disparar eventos.

Mediante este mecanismo, Node.js puede manejar tareas asíncronas de manera eficiente; incluso en un entorno de un solo hilo puede lograr procesamiento concurrente.

Todo el flujo de la programación controlada por eventos se implementa así, de forma muy concisa. Es algo similar al patrón observador: el evento equivale a un tema (Subject), y todas las funciones de procesamiento registradas en ese evento equivalen a los observadores (Observer).

Conceptos básicos:

  • Evento: una acción o cambio de estado que ocurre en el programa, por ejemplo, la finalización de la lectura de un archivo o la llegada de una solicitud HTTP.
  • El emisor de eventos:EventEmitteres el módulo integrado de Node.js que se utiliza para emitir y escuchar eventos.
  • Manejador de eventos: función de devolución de llamada asociada al evento, que se invoca cuando ocurre el evento.

Flujo de la programación controlada por eventos:

  • Registrar evento: en el programa, medianteEventEmitteruna instancia de EventEmitter se registran eventos y sus manejadores correspondientes.
  • Disparar evento: cuando ocurre el evento especificado,EventEmitterse disparará el evento.
  • Procesar evento: el bucle de eventos programará la función de devolución de llamada correspondiente para ejecutar la tarea.

Node.js tiene múltiples eventos integrados. Podemos importar el módulo events y, al instanciar la clase EventEmitter, vincular y escuchar eventos, como en el siguiente ejemplo:

// 引入 events 模块
var events = require('events');
// 创建 eventEmitter 对象
var eventEmitter = new events.EventEmitter();

El siguiente programa vincula el manejador de eventos:

// 绑定事件及事件的处理程序
eventEmitter.on('eventName', eventHandler);

Podemos disparar el evento mediante el programa:

// 触发事件
eventEmitter.emit('eventName');

Ejemplo

Crea un archivo hello.js, con el siguiente código:

Ejemplo 1

const EventEmitter = require('events');
const myEmitter = new EventEmitter();

// Registrar el manejador de eventos
myEmitter.on('greet', () => {
  console.log('Hello, world!');
});

// Disparar el evento
myEmitter.emit('greet');

Salida:

Hello, world!

Crea un archivo main.js, con el siguiente código:

Ejemplo 2

//Importar el módulo events var events = require('events'); //Crear un objeto eventEmitter var eventEmitter = new events.EventEmitter(); //Crear el manejador de eventos var connectHandler = function connected() { console.log('Conexión exitosa.'); //Disparar el evento data_received eventEmitter.emit('data_received'); } //Vincular el manejador del evento connection eventEmitter.on('connection', connectHandler); //Usar una función anónima para vincular el evento data_received eventEmitter.on('data_received', function(){ console.log('Datos recibidos exitosamente.'); }); //Disparar el evento connection eventEmitter.emit('connection'); console.log("El programa terminó de ejecutarse.");

A continuación, ejecutemos el código anterior:

$ node main.js
连接成功。
数据接收成功。
程序执行完毕。

¿Cómo funciona una aplicación Node?

En una aplicación Node, las funciones que ejecutan operaciones asíncronas toman la función de devolución de llamada como último parámetro, y la función de devolución de llamada recibe el objeto de error como primer parámetro.

Ahora veamos nuevamente el ejemplo anterior. Crea un archivo input.txt con el siguiente contenido:

Ejemplo官网地址:www.example.com

Crea un archivo main.js con el siguiente código:

var fs = require("fs");

fs.readFile('input.txt', function (err, data) {
   if (err){
      console.log(err.stack);
      return;
   }
   console.log(data.toString());
});
console.log("程序执行完毕");

En el programa anterior, fs.readFile() es una función asíncrona que se utiliza para leer archivos. Si ocurre un error durante la lectura del archivo, el objeto de error err mostrará el mensaje de error.

Si no ocurre ningún error, readFile omite la salida del objeto err, y el contenido del archivo se muestra a través de la función de devolución de llamada.

Ejecuta el código anterior; el resultado es el siguiente:

程序执行完毕
Ejemplo官网地址:www.example.com

Ahora eliminamos el archivo input.txt; el resultado de la ejecución es el siguiente:

程序执行完毕
Error: ENOENT, open 'input.txt'

Debido a que el archivo input.txt no existe, se muestra el mensaje de error.

Otras extensiones