¿Cómo funciona Event Loop en JavaScript?

Si bien puede requerir una comprensión profunda de lenguajes como C ++ y C para escribir código de producción a gran escala, JavaScript a menudo se puede escribir con solo una comprensión básica de lo que se puede hacer con el lenguaje.

Los conceptos, como pasar devoluciones de llamadas a funciones o escribir código asincrónico, a menudo no son tan difíciles de implementar, lo que hace que a la mayoría de los desarrolladores de JavaScript les importe menos lo que sucede debajo del capó. Simplemente no les importa comprender las complejidades que el lenguaje les ha abstraído profundamente.

Como desarrollador de JavaScript, se vuelve cada vez más importante comprender lo que realmente sucede bajo el capó y cómo funcionan realmente la mayoría de estas complejidades abstraídas de nosotros. Nos ayuda a tomar decisiones más informadas, lo que, a su vez, puede aumentar drásticamente el rendimiento de nuestro código.

Este artículo se centra en uno de los conceptos o términos muy importantes pero rara vez entendidos en JavaScript. ¡El BUCLE DE EVENTOS!.

No se puede evitar escribir código asíncrono en JavaScript, pero ¿por qué significa realmente un código que se ejecuta de forma asíncrona? es decir, el bucle de eventos

Antes de que podamos entender cómo funciona el bucle de eventos, primero tenemos que entender qué es JavaScript y cómo funciona.

¿Qué es JavaScript?

Antes de continuar, me gustaría que demos un paso atrás a lo básico. ¿Qué es realmente JavaScript? Podríamos definir JavaScript como;

JavaScript es un lenguaje de alto nivel, interpretado, de un solo subproceso, sin bloqueo, asincrónico y concurrente.

Espera, ¿qué es esto? ¿Una definición libresca? 🤔

¡Vamos a romperlo!

Las palabras clave aquí con respecto a este artículo son subproceso único, sin bloqueo, concurrente y asíncrono.

Hilo único

Un hilo de ejecución es la secuencia más pequeña de instrucciones programadas que un planificador puede gestionar de forma independiente. Un lenguaje de programación es de subproceso único, lo que significa que solo puede realizar una tarea u operación a la vez. Esto significa que ejecutaría un proceso completo de principio a fin sin que el subproceso se interrumpa o detenga.

A diferencia de los lenguajes de subprocesos múltiples, donde se pueden ejecutar múltiples procesos en varios subprocesos al mismo tiempo sin bloquearse entre sí.

¿Cómo puede JavaScript ser de un solo subproceso y sin bloqueo al mismo tiempo?

Pero, ¿qué significa bloquear?

sin bloqueo

No existe una definición única de bloqueo; simplemente significa cosas que se ejecutan lentamente en el hilo. Entonces, no bloquear significa cosas que no son lentas en el hilo.

Pero espera, ¿dije que JavaScript se ejecuta en un solo hilo? Y también lo dije sin bloqueo, lo que significa que la tarea se ejecuta rápidamente en la pila de llamadas. ¿¿¿Pero cómo??? ¿Qué tal cuando ejecutamos temporizadores? Bucles?

¡Relax! Nos enteraríamos en un rato 😉.

Concurrente

Concurrencia significa que el código se ejecuta simultáneamente por más de un subproceso.

De acuerdo, las cosas se están poniendo realmente extraño ahora, ¿cómo puede JavaScript ser de un solo subproceso y ser concurrente al mismo tiempo? es decir, ejecutando su código con más de un hilo?

Asincrónico

La programación asincrónica significa que el código se ejecuta en un bucle de eventos. Cuando hay una operación de bloqueo, se inicia el evento. El código de bloqueo sigue ejecutándose sin bloquear el hilo de ejecución principal. Cuando el código de bloqueo termina de ejecutarse, pone en cola el resultado de las operaciones de bloqueo y las vuelve a colocar en la pila.

¿Pero JavaScript tiene un solo hilo? Entonces, ¿qué ejecuta este código de bloqueo mientras permite que se ejecuten otros códigos en el hilo?

Antes de continuar, hagamos un resumen de lo anterior.

  • JavaScript es de un solo subproceso
  • JavaScript no bloquea, es decir, los procesos lentos no bloquean su ejecución
  • JavaScript es concurrente, es decir, ejecuta su código en más de un hilo al mismo tiempo
  • JavaScript es asíncrono, es decir, ejecuta código de bloqueo en otro lugar.

Pero lo anterior no cuadra exactamente, ¿cómo puede un lenguaje de subproceso único ser no bloqueante, concurrente y asíncrono?

Profundicemos un poco más, vayamos a los motores de tiempo de ejecución de JavaScript, V8, tal vez tenga algunos hilos ocultos que no conocemos.

Motor V8

El motor V8 es un motor de tiempo de ejecución de ensamblaje web de código abierto y alto rendimiento para JavaScript escrito en C++ por Google. La mayoría de los navegadores ejecutan JavaScript usando el motor V8, e incluso el popular entorno de tiempo de ejecución de node js también lo usa.

En inglés simple, el V8 es un programa C++, que recibe código JavaScript, lo compila y lo ejecuta.

El V8 hace dos cosas importantes;

  • Asignación de memoria en montón
  • Contexto de ejecución de la pila de llamadas

Lamentablemente, nuestra sospecha estaba equivocada. El V8 tiene solo una pila de llamadas, piense en la pila de llamadas como el hilo.

Un subproceso === una pila de llamadas === una ejecución a la vez.

Imagen – Hacker mediodía

Dado que V8 tiene solo una pila de llamadas, ¿cómo se ejecuta JavaScript de forma simultánea y asíncrona sin bloquear el hilo de ejecución principal?

Tratemos de averiguarlo escribiendo un código asíncrono simple pero común y analicémoslo juntos.

JavaScript ejecuta cada código línea por línea, uno tras otro (un solo subproceso). Como era de esperar, la primera línea se imprime aquí en la consola, pero ¿por qué se imprime la última línea antes del código de tiempo de espera? ¿Por qué el proceso de ejecución no espera el código de tiempo de espera (bloqueo) antes de ejecutar la última línea?

Algún otro subproceso parece habernos ayudado a ejecutar ese tiempo de espera, ya que estamos bastante seguros de que un subproceso solo puede ejecutar una sola tarea en cualquier momento.

Echemos un vistazo a la Código fuente V8 por un momento.

¡¡¡¿¿Esperar lo??!!! No hay funciones de temporizador en V8, ¿no hay DOM? ¿No hay eventos? ¿Sin AJAX?…. ¡¡¡Síiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii

Los eventos, DOM, temporizadores, etc. no forman parte de la implementación central de JavaScript. JavaScript se ajusta estrictamente a las especificaciones de Ecma Scripts y, a menudo, se hace referencia a varias versiones de acuerdo con sus especificaciones de Ecma Scripts (ES X).

Flujo de trabajo de ejecución

Los navegadores proporcionan eventos, temporizadores y solicitudes Ajax en el lado del cliente y, a menudo, se denominan API web. ¡Son los que permiten que el JavaScript de subproceso único no bloquee, sea concurrente y asíncrono! ¿Pero cómo?

Hay tres secciones principales en el flujo de trabajo de ejecución de cualquier programa de JavaScript, la pila de llamadas, la API web y la cola de tareas.

La pila de llamadas

Una pila es una estructura de datos en la que el último elemento agregado es siempre el primero en eliminarse de la pila, podría pensar en ella como una pila de un plato en el que solo el primer plato que fue el último agregado se puede eliminar primero. Una pila de llamadas no es más que una estructura de datos de pila donde las tareas o el código se ejecutan en consecuencia.

Consideremos el siguiente ejemplo;

Fuente: https://youtu.be/8aGhZQkoFbQ

Cuando llama a la función printSquare() , se inserta en la pila de llamadas, la función printSquare() llama a la función square(). La función square() se coloca en la pila y también llama a la función multiplicar(). La función multiplicar se empuja a la pila. Dado que la función de multiplicación regresa y es lo último que se envió a la pila, primero se resuelve y se elimina de la pila, seguida de la función square() y luego la función printSquare().

La API web

Aquí es donde se ejecuta el código que no es manejado por el motor V8 para no «bloquear» el hilo de ejecución principal. Cuando Call Stack encuentra una función de API web, el proceso se transfiere inmediatamente a Web API, donde se ejecuta y libera Call Stack para realizar otras operaciones durante su ejecución.

Volvamos a nuestro ejemplo anterior de setTimeout;

Cuando ejecutamos el código, la primera línea de console.log se empuja a la pila y obtenemos nuestra salida casi de inmediato, al llegar al tiempo de espera, los temporizadores son manejados por el navegador y no son parte de la implementación central de V8, se empujan a la API web en su lugar, liberando la pila para que pueda realizar otras operaciones.

Mientras el tiempo de espera aún se está ejecutando, la pila avanza a la siguiente línea de acción y ejecuta el último archivo console.log, lo que explica por qué lo generamos antes de la salida del temporizador. Una vez que se completa el temporizador, algo sucede. ¡Console.log in entonces el temporizador aparece mágicamente en la pila de llamadas de nuevo!

¿Cómo?

El bucle de eventos

Antes de discutir el ciclo de eventos, primero veamos la función de la cola de tareas.

Volviendo a nuestro ejemplo de tiempo de espera, una vez que la API web termina de ejecutar la tarea, no la vuelve a enviar automáticamente a la pila de llamadas. Va a la cola de tareas.

Una cola es una estructura de datos que funciona según el principio Primero en entrar, primero en salir, por lo que a medida que las tareas se colocan en la cola, salen en el mismo orden. Las tareas que han sido ejecutadas por las API web, que se envían a la Cola de tareas, luego regresan a la Pila de llamadas para imprimir su resultado.

Pero espera. ¿QUÉ DIABLOS ES EL BUCLE DE EVENTOS?

Fuente: https://youtu.be/8aGhZQkoFbQ

El bucle de eventos es un proceso que espera a que la Pila de llamadas se borre antes de enviar las devoluciones de llamada de la Cola de tareas a la Pila de llamadas. Una vez que la pila está vacía, el bucle de eventos se activa y comprueba la cola de tareas para ver si hay devoluciones de llamadas disponibles. Si hay alguno, lo empuja a la pila de llamadas, espera a que la pila de llamadas se borre nuevamente y repite el mismo proceso.

Fuente: https://www.quora.com/How-does-an-event-loop-work/answer/Timothy-Maxwell

El diagrama anterior demuestra el flujo de trabajo básico entre el bucle de eventos y la cola de tareas.

Conclusión

Si bien esta es una introducción muy básica, el concepto de programación asíncrona en JavaScript brinda suficiente información para comprender claramente lo que sucede debajo del capó y cómo JavaScript puede ejecutarse de manera simultánea y asíncrona con un solo hilo.

JavaScript siempre está bajo demanda, y si tiene curiosidad por aprender, le aconsejo que consulte este Curso de Udemy.