Javascript Event Loop, ejecución secuencial y ejecución asíncrona

Javascript tiene un modelo de ejecución de código donde utiliza un ciclo de eventos, el cual no es necesario conocerlo para empezar a programar, pero que una vez entendiendo su funcionamiento, se puede crear código mucho más eficiente, y resolver problemas relacionados rápidamente, que a simple vista no son tan obvios.

Para comenzar:

  • Javascript se ejecuta en un solo hilo de ejecución, como se ejecuta un solo hilo, su ejecución es secuencial, no es como JAVA donde se pueden lanzar otros hilos.
  • Existen ambientes huéspedes que tienen su propia API y que pueden usar hilos, ejemplo:
    • Navegador web
    • Node.js
  • Este funcionamiento secuencial es el mismo no cambia por estar relacionado con un ambiente huesped.
  • La concurrencia se logra a través de invocaciones asíncronas a la API del ambiente huésped, esto evita que el funcionamiento se bloquee y provoque lentitud de las aplicaciones.

Elementos para la concurrencia

Veamos con más detalle los elementos que hacen funcionar Javascript en los ambientes huéspedes.

  1. Call Stack – Pila de ejecuciones
  2. Callback Queue – Cola de retrollamadas
  3. Event Loop – Ciclo de eventos
  4. Host Environment – Funcionalidad del ambiente huésped
Diagrama del ciclo de eventos Javascript

Calls Stack, heap y motor de javascript

Del lado izquierdo tenemos al motor de javascript, este motor se encarga de ejecutar nuestro código, a veces es llamado maquina virtual, porque se encarga de interpretar nuestro código a lenguaje maquina y ejecutarlo. Solo puede ejecutar una línea de código a la vez. Tiene un Heap para guardar objetos y demás datos en memoria que el código pueda generar.

Pero también tenemos una Pila de llamadas (Call Stack), que también son objetos y datos de memoria, pero tienen una estructura especifica, tienen un orden y el primer elemento en entrar es el último en salir, se le llama LIFO (Last-in, First-out).

Cada elemento de la pila es un trozo de código Javascript y guarda las referencias del contexto y variables utilizadas, la pila ejecuta el trozo de código en turno hasta quedar vacía.

Callbacks Queue

Abajo tenemos Callbacks Queue, es una estructura de datos y guarda pedazos de código que el motor de javascript puede ejecutar. Es una cola, y como la cola de las tortillas el primero en formarse, es el primero en salir (FIFO, First-in, First-out).

Un callback o retrollamada, es una función que se pasa por parámetro a otra, de tal manera que esta última ejecuta el callback en un punto determinado, cuando las condiciones y/o los datos cumplen una cierta regla de invocación.

Event loop

El famosísimo Event Loop, este elemento nos permite mover, de Callbacks Queue el siguiente pedazo de código a ejecutarse hacia el Call Stack, como es un ciclo, en cada iteración toma un solo callback.

Ambiente huésped

Del lado derecho tenemos El ambiente huésped, el ambiente huésped es el lugar donde corremos Javascript, los más comunes son el Navegador web y Node.js. Las tareas que ejecuta el ambiente huésped son independientes de los demás componentes. En el navegador web tiene sus propias API como fetch, setTimeout, alert.

¿Cómo es posible?, ¿Cómo puede ser concurrente? ¿Cómo puedo hacer muchas cosas a la vez en un sitio web?

Las APIs del navegador web generan callbacks cuando su tarea ya está completada y los agrega al Callbacks Queue para que el event loop las tome y las mueva al Call stack.

Entonces siguiendo el diagrama, supongamos que inicio es una función, con lo que se inicia el proceso de ejecución en el call stack, dentro de inicio tenemos cuatro setTimeout de dos segundos, la función setTimeout es propia del ambiente huésped, y a los cuatro setTimeouts le pasamos un callback, cb1, cb2, cb3 y cb4.  Los temporizadores setTimeout se ejecutan en la parte de la funcionalidad del ambiente huésped dejando tiempo de ejecución para el call stack, he aquí la concurrencia.

Después de los primeros dos segundos el primer setTimeout pasa cb1 al callback queue, pero durante estos dos segundos se pueden realizar otras operaciones en el ambiente huésped, en el caso del navegador, un ejemplo seria que el usuario puede seguir escribiendo dentro de un textarea. De nuevo, he aqui la concurrencia.

Tras el primer setTimeout, el segundo setTimeout pasa cb2 al callback queue y así sucesivamente hasta el cb4. Mientras los callbacks se pasan al callback queue, el event loop nunca deja de funcionar, por lo que en alguna iteración el event Loop pasa cb1 al call stack y se empieza a ejecutar el código de cb1 y esto mismo sucede con cb2, cb3 y cb4.

De esta forma de ejecutar código podemos indicar lo siguiente:

  1. Las APIs o funciones huéspedes reciben callbacks que deben insertar en la cola cuando el trabajo que les corresponde esta hecho, por ejemplo un callback de una petición ajax se pasa a la cola solo cuando la petición ya obtuvo la respuesta. En el ejemplo de setTimeout, cuando pasen dos segundos, se inserta el callback al call stack. Mientras el ambiente huésped realiza sus tareas, el call stack de ejecución de javascript puede ejecutar el código que le corresponda.
  2. El Event loop es un ciclo infinito que mientras existan callbacks en la cola, pasara cada callback, uno por uno, al call stack.
  3. El call stack no puede ejecutar más de un código a la vez, va ejecutando desde el último elemento hasta el primero, en nuestro caso hasta la terminación de la función inicio que dio origen a todo el proceso.

Gracias a que los callbacks se ejecutan hasta que un trabajo específico esté terminado, proporciona una manera asíncrona de ejecutar tareas. Las tareas pueden realizarse en el ambiente huésped sin afectar la ejecución del call stack, en la pila solo se ejecuta código que recibe el resultado de las tareas realizadas por el ambiente huésped.

Aquí abajo esta el ejemplo del que hablamos, el temporizador genera los callbacks en el orden que aparecen los setTimeouts más dos segundos y los agrega a la pila. El Event Loop toma un callback en cada iteración, por lo que cada cb de setTimeout se invocan uno después del otro y no exactamente después de dos segundos, pues entre que se posicionan los callbacks en la cola y se mueven al call stack de ejecución transcurre más tiempo, sin contar el tiempo que trascurre entre cada invocación de setTimeout.

Si en los cuatro callbacks se indica el mismo tiempo en milisegundos, presiona el botón RERUN, Te darás cuenta de que los tiempos no son iguales y que pueden pasar más de dos segundos.

Jaime Cervantes

Deja un comentario

Salir de la versión móvil