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

Javascript tiene un modelo de concurrencia basado en un ciclo de eventos (event loop), 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 rápidamente. En este artículo se explicará su funcionamiento en el navegador web, pero es el mismo para otros ambientes huésped, si, Javascript se puede ejecutar en otros ambientes que no sean el navegador, por ejemplo en node.js.

Existe una cosa llamada Javascript Engine (Motor/Interprete de Javascript) que se encarga de ejecutar nuestro código, este Javascript Engine a veces es llamado maquina virtual, porque se encarga de interpretar nuestro código a lenguaje maquina y ejecutarlo. Este JavaScript Engine/Maquina Virtual es de un solo proceso porque no emula un sistema operativo completo, sino solo el código que usamos en el ambiente huésped. Otro ejemplo de maquina virtual de un solo proceso es Wine que corre programas de windows en Linux.

La manera en que se ejecuta nuestro código en Javascript es secuencial y solo se puede ejecutar una línea de código a la vez, entonces nos preguntamos, ¿Como es posible?, ¿Cómo puede ser concurrente? ¿Cómo puedo hacer muchas cosas a la vez en un sitio web? La respuesta está en el siguiente diagrama:

Diagrama del ciclo de eventos Javascript

No es el mejor diagrama, pero podemos ver la relación de los componentes:

  1. Callback Stack – Pila de callbacks
  2. Callback Queue – Cola de callbacks
  3. Event Loop – Ciclo de eventos
  4. Host Environment – Funcionalidad del ambiente huésped

El JavaSccrit Engine como mencionamos antes, ejecuta el código secuencialmente y solo puede ejecutar una línea de código a la vez. Tiene un Heap/Monticulo para guardar objetos y demás datos en memoria que el código pueda generar. Pero también tenemos un Stack, que también son objetos y datos de memoria, pero tienen un estructura especifica, tienen un orden y el primer elemento en entrar es el último en salir, se le llama estructura de datos LIFO (Last-in, First-out). Cada elemento de la pila guarda las referencias del contexto y variables utilizadas en el código que va a ejecutar, así es, la pila ejecuta el pedazo de código en turno hasta quedar vacía.

Luego tenemos Callbacks Queueque es parecida al Stack/Pila, es una estructura de datos y guarda el siguiente pedazo de código (callback) que deberá ejecutarse en la Stack/Pila, como dije, 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 (referencia o puntero) a otra, de tal manera que esta ultima ejecuta a la anterior en un punto determinado, cuando las condiciones y/o los datos cumplen una cierta regla de invocación.

El famosisimo Event Loop/Ciclo de eventos, este elemento nos permite mover, de Callbacks Queue el siguiente pedazo de código a ejecutarse hacia el Stack, como es un ciclo, en cada iteración toma un callback y lo pasa al stack de ejecución.

Por último tenemos El ambiente huésped, el ambiente huésped es el lugar donde utilizamos Javascript, los más comunes son el Navegador weby Node.js. Las tareas que ejecuta el ambiente huésped son independientes de los demás componentes del Event Loop.

Las APIs del navegador web generan callbackscuando su tarea ya esta completada y los agrega al Callbacks Queue para que el event looplas tome y las mueva al stackde ejecución. Las tareas ejecutadas en el Ambiente huésped, como las APIs del navegador web y las APIs de node.js

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

Después de los primeros 2 segundos el primer setTimeout pasa cb1 a la cola, pero durante estos 2 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.

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

De lo anterior podemos deducir algunas cosas:

  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 2 segundos, insertara el callback a la cola. Mientras el ambiente huésped realiza sus tareas, el stack de ejecución de javascript puede ejecutar el código que le corresponda. De nuevo, he aquí la concurrencia
  2. El Event loop es un ciclo infinito que mientras existan callbacks en la cola, pasara cada callback, uno por uno, a la stack/pilade ejecución.
  3. El stack/pila de ejecución 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 especifico este terminado, proporciona una manera asíncrona de ejecutar tareas. Las tareas pueden realizarse en el ambiente huésped sin afectar la ejecución del stack/pila, 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 2 segundos y los agrega a la pila. El Event Loop toma un callback en cada iteracion, por lo que cada cb de setTimeout se invocan uno después del otro y no exactamente después de 2 segundos, pues entre que se posicionan los callbacks en la cola y se mueven al stack de ejecución transcurre más tiempo, sin contar el tiempo que trascurre entre cada invocación de setTimeout.

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

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

This site uses Akismet to reduce spam. Learn how your comment data is processed.