El ciclo de eventos de Node.js

El bucle de eventos es uno de los aspectos más importantes para comprender acerca de Node

Introducción

losBucle de eventoses uno de los aspectos más importantes para comprender sobre Node.

¿Por qué es esto tan importante? Porque explica cómo Node puede ser asíncrono y tener E / S sin bloqueo, por lo que explica básicamente la "aplicación asesina" de Node, lo que lo hizo tan exitoso.

El código JavaScript de Node.js se ejecuta en un solo hilo. Solo está sucediendo una cosa a la vez.

Esta es una limitación que en realidad es muy útil, ya que simplifica mucho la forma en que programa sin preocuparse por problemas de concurrencia.

Solo necesita prestar atención a cómo escribe su código y evitar cualquier cosa que pueda bloquear el hilo, como llamadas de red síncronas o infinitasbucles.

En general, en la mayoría de los navegadores hay un bucle de eventos para cada pestaña del navegador, para aislar cada proceso y evitar una página web con bucles infinitos o un procesamiento pesado que bloquee todo el navegador.

El entorno gestiona múltiples bucles de eventos simultáneos, para manejar llamadas a API, por ejemplo.Trabajadores webtambién se ejecutan en su propio bucle de eventos.

Principalmente debes preocuparte de quetu codigose ejecutará en un solo bucle de eventos y escribirá código teniendo esto en cuenta para evitar bloquearlo.

Bloquear el bucle de eventos

Cualquier código JavaScript que tarde demasiado en devolver el control al bucle de eventos bloqueará la ejecución de cualquier código JavaScript en la página, incluso bloqueará el hilo de la interfaz de usuario y el usuario no podrá hacer clic, desplazarse por la página, etc.

Casi todas las primitivas de E / S en JavaScript no son bloqueantes. Solicitudes de red, operaciones del sistema de archivos, etc. El bloqueo es la excepción, y esta es la razón por la que JavaScript se basa tanto en devoluciones de llamada y, más recientemente, enpromesasyasync / await.

La pila de llamadas

La pila de llamadas es una cola LIFO (último en entrar, primero en salir).

El bucle de eventos comprueba continuamente elpila de llamadaspara ver si hay alguna función que deba ejecutarse.

Mientras lo hace, agrega cualquier llamada de función que encuentre a la pila de llamadas y ejecuta cada una en orden.

¿Conoce el seguimiento de la pila de errores con el que puede estar familiarizado, en el depurador o en la consola del navegador? El navegador busca los nombres de las funciones en la pila de llamadas para informarle qué función origina la llamada actual:

Exception call stack

Una explicación simple del ciclo de eventos

Escojamos un ejemplo:

yo suelofoo,barybazcomonombres aleatorios. Ingrese cualquier tipo de nombre para reemplazarlos.

const bar = () => console.log('bar')

const baz = () => console.log(‘baz’)

const foo = () => { console.log(‘foo’) bar() baz() }

foo()

Este código imprime

foo
bar
baz

como se esperaba.

Cuando se ejecuta este código, primerofoo()se llama. Dentrofoo()primero llamamosbar(), luego llamamosbaz().

En este punto, la pila de llamadas se ve así:

Call stack first example

El bucle de eventos en cada iteración busca si hay algo en la pila de llamadas y lo ejecuta:

Execution order first example

hasta que la pila de llamadas esté vacía.

Ejecución de la función de cola

El ejemplo anterior parece normal, no tiene nada de especial: JavaScript encuentra cosas para ejecutar, las ejecuta en orden.

Veamos cómo diferir una función hasta que la pila esté limpia.

El caso de uso desetTimeout(() => {}), 0)es llamar a una función, pero ejecutarla una vez que se hayan ejecutado todas las demás funciones del código.

Toma este ejemplo:

const bar = () => console.log('bar')

const baz = () => console.log(‘baz’)

const foo = () => { console.log(‘foo’) setTimeout(bar, 0) baz() }

foo()

Este código se imprime, quizás sorprendentemente:

foo
baz
bar

Cuando se ejecuta este código, primero se llama a foo (). Dentro de foo () primero llamamos setTimeout, pasandobarcomo argumento, y le indicamos que se ejecute inmediatamente lo más rápido posible, pasando 0 como temporizador. Luego llamamos a baz ().

En este punto, la pila de llamadas se ve así:

Call stack second example

Aquí está el orden de ejecución de todas las funciones de nuestro programa:

Execution order second example

¿Por qué está pasando esto?

La cola de mensajes

Cuando se llama a setTimeout (), el navegador o Node.js inician elTemporizador. Una vez que expira el temporizador, en este caso inmediatamente, cuando ponemos 0 como tiempo de espera, la función de devolución de llamada se coloca en elCola de mensajes.

La cola de mensajes también es donde los eventos iniciados por el usuario, como los eventos de clic o teclado, oha podido recuperarlas respuestas se ponen en cola antes de que su código tenga la oportunidad de reaccionar ante ellas. O tambienDOMeventos comoonLoad.

El bucle da prioridad a la pila de llamadas, y primero procesa todo lo que encuentra en la pila de llamadas, y una vez que no hay nada allí, va a recoger cosas en la cola de mensajes.

No tenemos que esperar a funciones comosetTimeout, buscar u otras cosas para hacer su propio trabajo, porque son proporcionadas por el navegador y viven en sus propios hilos. Por ejemplo, si configura elsetTimeouttiempo de espera de 2 segundos, no tiene que esperar 2 segundos; la espera ocurre en otro lugar.

Cola de trabajos de ES6

ECMAScript 2015introdujo el concepto de cola de trabajos, que es utilizado por Promises (también introducido en ES6 / ES2015). Es una forma de ejecutar el resultado de una función asíncrona lo antes posible, en lugar de colocarlo al final de la pila de llamadas.

Las promesas que se resuelven antes de que finalice la función actual se ejecutarán inmediatamente después de la función actual.

Encuentro agradable la analogía de un paseo en montaña rusa en un parque de atracciones: la cola de mensajes te pone al final de la cola, detrás de todas las demás personas, donde tendrás que esperar tu turno, mientras que la cola de trabajos es el ticket de paso rápido. que te permite realizar otro viaje justo después de terminar el anterior.

Ejemplo:

const bar = () => console.log('bar')

const baz = () => console.log(‘baz’)

const foo = () => { console.log(‘foo’) setTimeout(bar, 0) new Promise((resolve, reject) => resolve(‘should be right after baz, before bar’) ).then(resolve => console.log(resolve)) baz() }

foo()

Esto imprime

foo
baz
should be right after baz, before bar
bar

Esa es una gran diferencia entre Promises (y Async / await, que se basa en promesas) y las viejas funciones asincrónicas a través desetTimeout()u otras API de plataforma.

Conclusión

Este artículo le presentó los componentes básicos del bucle de eventos de Node.js.

Esta es una parte esencial de cualquier programa escrito en Node.js, y espero que algunos de los conceptos explicados aquí te sean útiles en el futuro.

Descarga mi gratisManual de Node.js


Más tutoriales de nodos: