002. Asincronía y el Event Loop

La asincronía es uno de los pilares fundamentales de Javascript. Javascript es un lenguaje de un sólo subproceso o hilo, lo que se conoce como Single thread, lo que significa que sólo puede ejecutar una cosa a la vez.

Entender  conceptos

Es muy importante entender los siguientes conceptos:

  • Procesamiento Single thread y Multi thread.
  • Operaciones de CPU y operaciones de I/O.
  • Operaciones concurrentes y paralelas.
  • Operaciones bloqueantes y no bloqueantes.
  • Operaciones síncronas y asíncronas.

Javascript es un lenguaje Single thread, y si bien los idiomas de un sólo hilo simplifican la escritura de código, ya que no hay que preocuparse por los problemas de concurrencia, como por ejm, el recolector de basura que existe en otros lenguajes de programación. Esto también significa que no se pueden hacer operaciones largas como el acceso a la red sin que se bloquee el hilo principal. Es una de las principales preocupaciones en un lenguaje que trabaja en un sólo hilo como es Javascript.

Imaginemos que solicitamos datos a una API, dependiendo de la situación de la red, del servidor… esto puede tardar mucho o poco tiempo en procesar dicha solicitud, y mientras, el hilo principal de nuestro código se quedara bloqueado, y haría que la página web no respondiera. Aquí es donde entra en juego la asincronía, que se encarga de realizar largas solicitudes de red sin bloquear el hilo principal.

Javascript es un lenguaje que fue diseñado para ser ejecutado en navegadores, trabajar con peticiones hacia la red y procesar las interacciones con el usuario, y ésto al mismo tiempo de tratar de mantener una interfaz lo más fluida posible.

Javascript trabaja bajo un modelo asíncrono y no bloqueante, y tiene un loop de eventos (event loop) implementado de un sólo hilo, lo que se conoce como Single thread para operaciones de entrada y salida. Gracias a ello es que Javascript es altamente concurrente a pesar de que sea un lenguaje de un sólo hilo.

Procesamiento Single thread y Multit hread

Los hilos son las unidades básicas de ejecución de cada proceso que realiza nuestra máquina. Cada vez que abrimos nuestro navegador o nuestro editor de código, en nuestra computadora se levanta un proceso, e internamente estos procesos pueden hacer correr varios hilos o un sólo hilo, que es lo que ejecuta justamente su funcionalidad. Dependiendo de las características del lenguaje, hay lenguajes que trabajan en un sólo hilo, los denominados lenguajes Single thread, y lenguajes que trabajan en multi hilos, o lenguajes Multi thread. Javascript tiene un sólo hilo de ejecución.

La explicación que se está dando sirve tanto para el ambiente de los navegadores como para el ambiente del servidor, vía Node.jsJavascript tiene diferentes mecanismos para trabajar la asincronía, el primer mecanismo que tenemos son las funciones de tipo Callback.

Hay un concepto denominado Call Stack, en el cual se van apilando las tareas, y dependiendo de sin son síncronas o asíncronas, podemos ver como cada una de ellas se va liberando. Javascript trabaja bajo una filosofía denominada LIFO (Last In / First Out), que lo que significa es que la última tarea en entrar es la primera en salir. Esta es la filosofía de la manera en como se van ejecutando las operaciones.

Operaciones de CPU y operaciones de I/O

En un procesamiento, en el código de la operación podemos tener operaciones de CPU, o bien operaciones de entrada / salida. Las operaciones de CPU son las que pasan el mayor tiempo consumiendo los procesos de nuestra CPU. Por otro lado tenemos las operaciones de entrada y salida (I/O) que son aquellas que pasan la mayor parte del tiempo esperando la petición del recurso que han solicitado (por ejm, enviar un formulario a que se procese en un servidor y nos envíe la notificación de que se ha enviado nuestra petición, cuando estamos haciendo un pago en linea, estamos esperando a que un API cobre y nos dé el OK de todo correcto y hecho el pago, o cuando solicitamos datos a una API y nos devuelve los datos en formato JSON…).

En Javascript podemos ejecutar ambas, pero en la mayoría de los casos, por las características del lenguaje, Javascript se va a comportar haciendo operaciones de entrada y salida.

Concurrencia y paralelismo

La concurrencia es cuando dos o más tareas progresan simultáneamente, es decir, se están ejecutando al mismo tiempo y avanzando simultáneamente, mientras que el paralelismo es cuando dos o más tareas se ejecutan al mismo tiempo. La concurrencia podría parecer lo mismo, pero la clave está en la palabra progresar, algo es concurrente cuando diferentes tareas están progresando simultáneamente, es decir, al mismo tiempo, pero una pudo empezar antes y otra después. Podemos tener concurrencias en un entorno síncrono y en un entorno asíncrono.

Normalmente, Single thread está más relacionado a concurrencia, a no bloqueante y asíncrono, pero, por ejm, Javascript es Single thread, y podemos tener operaciones síncronas y asíncronas.

Operaciones bloqueantes y no bloqueantes

Lo bloqueante y no bloqueante se refiere a la fase de espera (siempre que se está ejecutando nuestro código hay una fase de espera). Una operación bloqueante o no bloqueante se refiere a como toma esa fase de espera, una operación bloqueante es aquella que no devuelve el control a la aplicación hasta que haya terminado toda su tarea. Las operaciones no bloqueantes son aquellas que se ejecutan, y devuelven inmediatamente el control al hilo principal, no importando si han terminado o no la tarea, en el momento que una tarea no bloqueante se acabe mandará una notificación y entonces se avisará al hilo principal.

Operaciones síncronas y asíncronas

Lo síncrono y asíncrono se refiere a cuando tendrá lugar la respuesta. Pensando en el presente, pasado y futuro, síncrono significa que la respuesta sucede en el presente, es decir, en el tiempo inmediato, una operación síncrona espera el resultado. Una operación asíncrona, la respuesta sucede en un futuro, es decir, se ejecuta, pero no sabe cuando va a venir la respuesta. La operación asíncrona no va a esperar el resultado, es por ello que suelta inmediatamente el control y se lo devuelve al hilo principal, por eso es que generalmente se suelen asociar los conceptos de bloqueante con síncrono, y no bloqueante con asíncrono, pero incluso puede existir código síncrono bloqueante y no bloqueante. El código asíncrono, prácticamente siempre va a ser no bloqueante.

Dos tipos de código en Javascript

En Javascript vamos a tener dos tipos de código:

  • Código síncrono bloqueante.
  • Código asíncrono no bloqueante.

Existe una herramienta que nos permite ver como va fluyendo todo el código, se llama Loupe. Esta herramienta lo que permite es ver de una manera visual como se van llenando las operaciones.

Ejm de código síncrono y asíncrono

      // Ejm de código síncrono bloqueante
      (() => {
        console.log("Código síncrono");
        console.log("Inicio");
        function dos() {
          console.log("Dos");
        }

        function uno() {
          console.log("Uno");
          dos();
          console.log("Tres");
        }

        uno();
        console.log("Fin");
      })();

      // Ejm de código asíncrono no bloqueante
      (() => {
        console.log("Código asíncrono");
        console.log("Inicio");
        function dos() {
          setTimeout(function () {
            console.log("Dos");
          }, 1000);
        }

        function uno() {
          setTimeout(function () {
            console.log("Uno");
          }, 0);
          dos();
          console.log("Tres");
        }

        uno();
        console.log("Fin");
      })();

En el segundo caso, al envolver las funciones en una función setTimeout(), hace que se pase a la pila de tareas, por lo que lo primero que se ejecuta es el código asíncrono bloqueante, y posteriormente el asíncrono no bloqueante, es decir, el código dentro de un setTimeout(). Los console.log() tienen mayor preferencia a la hora de ejecutarse que las funciones setTimeout(), por ello se ejecutan antes, ya que el setTimeout() depende de un tiempo, aunque sea cero.

En resumen

En resumen, Javascript usa un modelo asíncrono y no bloqueante con un loop de eventos implementado en un sólo hilo (Single thread) para operaciones de entrada y salida (input/otuput). En los próximos capítulos veremos que Javascript tiene diferentes mecanismos para trabajar las sincronías.

Scroll al inicio