Bucle de eventos y modelo de concurrencia de JavaScript

El tiempo de ejecución de Javascript es de un solo hilo, lo que significa que puede ejecutar un fragmento de código a la vez. Para comprender el modelo de concurrencia y el bucle de eventos en Javascript, primero debemos conocer algunos términos comunes que están asociados con él.

La pila de llamadas

Primero, aprendamos qué es una pila de llamadas.

Una pila de llamadas es una estructura de datos simple que registra en qué parte del código nos encontramos actualmente. Entonces, si entramos en una función que es una invocación de función, se envía a la pila de llamadas. Cuando volvemos de una función, se saca de la pila.

Veamos un ejemplo de código para comprender la pila de llamadas:

function multiply(x,y) { return x * y; } function squared(n) { return multiply(n,n) } function printSquare(n) { return squared(n) } let numberSquared = printSquare(5); console.log(numberSquared);

Primero, cuando el código se ejecuta, el tiempo de ejecución leerá cada una de las definiciones de función. Pero cuando llega a la línea donde se invoca la primera función printSquare (5) , empujará esta función a la pila de llamadas.

A continuación, se ejecutará esta función. Antes de regresar, encontrará otra función, al cuadrado (n) , por lo que suspenderá su operación actual y colocará esta función en la parte superior de la función existente.

Ejecuta la función (en este caso la función al cuadrado) y finalmente encuentra otra función multiplicar (n, n) . Luego suspende sus ejecuciones actuales e inserta esta función en la pila de llamadas. La función multiplicar se ejecuta y regresa con el valor multiplicado.

Finalmente, la función al cuadrado regresa y se extrae de la pila y luego lo mismo ocurre con printSquare. El valor al cuadrado final se asigna a la variable numberSquared.

Nos encontramos de nuevo con una invocación de función (en este caso, es una instrucción console.log ()), por lo que el tiempo de ejecución la envía a la pila. Esto lo ejecuta imprimiendo el número al cuadrado en la consola.

Tenga en cuenta que la primera función que se inserta en la pila antes de que se ejecute cualquiera de los códigos anteriores es la función principal. En el tiempo de ejecución, esto se denota como una "función anónima".

Entonces, para resumir: cada vez que se invoca una función, se inserta en la pila de llamadas donde se ejecuta. Finalmente, cuando la función finalice su ejecución y regrese implícita o explícitamente, se eliminará de la pila.

La pila de llamadas solo registra en qué momento qué función se estaba ejecutando. Y realiza un seguimiento de la función que se está ejecutando actualmente.

El navegador

Ahora sabemos por esto que Javascript puede ejecutar una cosa a la vez, pero ese no es el caso con el navegador. El navegador tiene su propio conjunto de API como setTimeout y XMLHttpRequests que no se especifican en el tiempo de ejecución de Javascript.

De hecho, si observa el código fuente de V8, el popular tiempo de ejecución de Javascript que impulsa a los navegadores como Google Chrome, no encontrará ninguna definición para él. Esto se debe a que estas API web especiales existen en el entorno del navegador, no dentro del entorno javascript. Entonces, puede decir que estas API introducen la simultaneidad en la mezcla.

Veamos un diagrama para comprender la imagen completa.

Modelo de bucle de eventos y concurrencia

Aquí se introducen algunos términos más, así que veámoslos:

Montón : es principalmente el lugar donde se asignan los objetos.

Cola de devolución de llamada : es una estructura de datos que almacena todas las devoluciones de llamada. Dado que es una cola, los elementos se procesan en función de FIFO, que es Primero en entrar, primero en salir.

Bucle de eventos : aquí es donde todas estas cosas se unen. El bucle de eventos simplemente verifica la pila de llamadas, y si está vacío (lo que significa que no hay funciones en la pila) toma la devolución de llamada más antigua de la cola de devolución de llamada y la empuja a la pila de llamadas que eventualmente ejecuta la devolución de llamada.

Entendamos esto con un ejemplo de código:

console.log('hi'); setTimeout(function() { console.log('freecodeCamp') },5000); console.log('JS')

Cuando se ejecuta la primera línea, es un console.log (). Esta es una invocación de función, lo que significa que esta función se inserta en la pila de llamadas donde ejecuta la impresión 'hola' en la consola. Finalmente se devuelve y se saca de la pila.

Luego, cuando el tiempo de ejecución va a ejecutar setTimeout (), sabe que se trata de una API web. Por lo tanto, lo entrega al navegador para que maneje su ejecución. El navegador inicia el temporizador y luego el tiempo de ejecución de JS saca el setTimeout () de la pila. Encuentra otra invocación de console.log () y, por lo tanto, la empuja a la pila de llamadas, el mensaje 'JS' se registra en la consola y, finalmente, se devuelve. Luego, el último console.log () se extrae de la pila. Ahora la pila de llamadas está vacía.

Mientras tanto, mientras todo esto sucedía, el temporizador termina. Cuando han transcurrido 5 segundos, el navegador sigue adelante y empuja la función de devolución de llamada a la cola de devolución de llamada.

A continuación, el ciclo de eventos comprueba si la pila de llamadas está libre o no. Como es gratuito, toma la función de devolución de llamada y la empuja nuevamente a la pila de llamadas que ejecuta el código dentro de ella.

De nuevo dentro del código hay una invocación de console.log () por lo que esta función va a la parte superior de la pila, ejecuta lo que registra 'freecodecamp' en la consola y finalmente regresa. Esto significa que se saca de la pila y finalmente la devolución de llamada se saca de la pila y terminamos.

Para visualizar esto mejor, pruebe esta herramienta de Phillip Roberts: Loupe Event Loop Visualizer