El modelo de ejecución de JavaScript tiene matices y es fácil de malinterpretar. Aprender sobre el bucle de eventos en su núcleo puede ayudar.

JavaScript es un lenguaje de subproceso único, creado para manejar tareas de una en una. Sin embargo, el bucle de eventos permite que JavaScript maneje eventos y devoluciones de llamadas de forma asincrónica mediante la emulación de sistemas de programación simultáneos. Esto asegura el rendimiento de sus aplicaciones JavaScript.

¿Qué es el bucle de eventos de JavaScript?

El bucle de eventos de JavaScript es un mecanismo que se ejecuta en segundo plano en todas las aplicaciones de JavaScript. Permite que JavaScript maneje tareas en secuencia sin bloquear su principal hilo de ejecución. Esto se conoce como programación asíncrona.

El bucle de eventos mantiene una cola de tareas para ejecutar y alimenta esas tareas a la derecha API web para ejecutar uno a la vez. JavaScript realiza un seguimiento de estas tareas y maneja cada una de acuerdo con el nivel de complejidad de la tarea.

instagram viewer

Comprender la necesidad del bucle de eventos de JavaScript y la programación asíncrona. Debe comprender qué problema resuelve esencialmente.

Tome este código, por ejemplo:

functionlongRunningFunction() {
// This function does something that takes a long time to execute.
for (var i = 0; i < 100000; i++) {
console.log("Hello")
}
}

functionshortRunningFunction(a) {
return a * 2 ;
}

functionmain() {
var startTime = Date.now();
longRunningFunction();

var endTime = Date.now();

// Prints the amount of time it took to execute functions
console.log(shortRunningFunction(2));
console.log("Time taken: " + (endTime - startTime) + " milliseconds");
}

main();

Este código primero define una función llamada función de ejecución larga (). Esta función realizará algún tipo de tarea compleja que requiere mucho tiempo. En este caso, realiza una para bucle iterando más de 100.000 veces. Esto significa que consola.log("Hola") se ejecuta 100.000 veces.

Dependiendo de la velocidad de la computadora, esto puede llevar mucho tiempo y bloquear función de ejecución corta() desde la ejecución inmediata hasta que se completa la función anterior.

Para el contexto, aquí hay una comparación del tiempo necesario para ejecutar ambas funciones:

Y luego el sencillo función de ejecución corta():

La diferencia entre una operación de 2351 milisegundos y una operación de 0 milisegundos es obvia cuando se trata de crear una aplicación de alto rendimiento.

Cómo ayuda el bucle de eventos con el rendimiento de la aplicación

El bucle de eventos tiene diferentes etapas y partes que contribuyen a que el sistema funcione.

La pila de llamadas

La pila de llamadas de JavaScript es esencial para la forma en que JavaScript maneja las llamadas a funciones y eventos desde su aplicación. El código JavaScript se compila de arriba a abajo. Sin embargo, Node.js, al leer el código, Node.js asignará llamadas a funciones de abajo hacia arriba. A medida que lee, inserta las funciones definidas como marcos en la pila de llamadas una por una.

La pila de llamadas es responsable de mantener el contexto de ejecución y el orden correcto de las funciones. Lo hace operando como una pila de último en entrar, primero en salir (LIFO).

Esto significa que el último marco de función que su programa inserte en la pila de llamadas será el primero en salir de la pila y ejecutarse. Esto asegurará que JavaScript mantenga el orden correcto de ejecución de funciones.

JavaScript sacará cada cuadro de la pila hasta que esté vacío, lo que significa que todas las funciones han terminado de ejecutarse.

API web de Libuv

En el núcleo de los programas asincrónicos de JavaScript se encuentra libuv. La biblioteca libuv está escrita en el lenguaje de programación C, que puede interactuar con el sistema operativo. API de bajo nivel. La biblioteca proporcionará varias API que permitirán que el código JavaScript se ejecute en paralelo con otros código. API para crear subprocesos, una API para comunicarse entre subprocesos y una API para administrar la sincronización de subprocesos.

Por ejemplo, cuando usas setTimeout en Node.js para pausar la ejecución. El temporizador se configura a través de libuv, que gestiona el bucle de eventos para ejecutar la función de devolución de llamada una vez que ha pasado el retraso especificado.

De manera similar, cuando realiza operaciones de red de forma asincrónica, libuv maneja esas operaciones de forma no bloqueante. asegurando que otras tareas puedan continuar el procesamiento sin esperar a que finalice la operación de entrada/salida (E/S). fin.

La cola de devolución de llamada y eventos

La cola de devolución de llamada y eventos es donde las funciones de devolución de llamada esperan su ejecución. Cuando se completa una operación asíncrona desde libuv, su función de devolución de llamada correspondiente se agrega a esta cola.

Así es como va la secuencia:

  1. JavaScript mueve las tareas asincrónicas a libuv para que las maneje y continúa manejando la siguiente tarea de inmediato.
  2. Cuando finaliza la tarea asincrónica, JavaScript agrega su función de devolución de llamada a la cola de devolución de llamada.
  3. JavaScript sigue ejecutando otras tareas en la pila de llamadas hasta que termina con todo en el orden actual.
  4. Una vez que la pila de llamadas está vacía, JavaScript mira la cola de devolución de llamadas.
  5. Si hay una devolución de llamada en la cola, empuja la primera a la pila de llamadas y la ejecuta.

De esta forma, las tareas asincrónicas no bloquean el subproceso principal y la cola de devolución de llamada garantiza que sus devoluciones de llamada correspondientes se ejecuten en el orden en que se completaron.

El ciclo del bucle de eventos

El bucle de eventos también tiene algo llamado cola de microtareas. Esta cola especial en el bucle de eventos contiene microtareas programadas para ejecutarse tan pronto como se complete la tarea actual en la pila de llamadas. Esta ejecución ocurre antes de la próxima iteración de bucle de evento o representación. Las microtareas son tareas de alta prioridad con precedencia sobre las tareas normales en el bucle de eventos.

Una microtarea se crea comúnmente cuando se trabaja con Promises. Siempre que una Promesa resuelva o rechace, su correspondiente .entonces() o .atrapar() callbacks se une a la cola de microtareas. Puede usar esa cola para administrar tareas que necesitan ejecución inmediata después de la operación actual, como actualizar la interfaz de usuario de su aplicación o manejar cambios de estado.

Por ejemplo, una aplicación web que realiza la obtención de datos y actualiza la interfaz de usuario en función de los datos recuperados. Los usuarios pueden activar esta obtención de datos haciendo clic en un botón repetidamente. Cada clic de botón inicia una operación de recuperación de datos asíncrona.

Sin microtareas, el ciclo de eventos para esta tarea funcionaría de la siguiente manera:

  1. El usuario hace clic en el botón repetidamente.
  2. Cada clic de botón desencadena una operación de obtención de datos asíncrona.
  3. A medida que se completan las operaciones de obtención de datos, JavaScript agrega sus devoluciones de llamada correspondientes a la cola de tareas normal.
  4. El bucle de eventos comienza a procesar tareas en la cola de tareas normal.
  5. La actualización de la interfaz de usuario basada en los resultados de la obtención de datos se ejecuta tan pronto como lo permitan las tareas habituales.

Sin embargo, con las microtareas, el ciclo de eventos funciona de manera diferente:

  1. El usuario hace clic en el botón repetidamente y activa una operación de obtención de datos asíncrona.
  2. A medida que se completan las operaciones de obtención de datos, el bucle de eventos agrega sus devoluciones de llamada correspondientes a la cola de microtareas.
  3. El bucle de eventos comienza a procesar tareas en la cola de microtareas inmediatamente después de completar la tarea actual (clic de botón).
  4. La actualización de la interfaz de usuario basada en los resultados de la obtención de datos se ejecuta antes de la siguiente tarea normal, lo que brinda una experiencia de usuario más receptiva.

Aquí hay un ejemplo de código:

const fetchData = () => {
returnnewPromise(resolve => {
setTimeout(() => resolve('Data from fetch'), 2000);
});
};

document.getElementById('fetch-button').addEventListener('click', () => {
fetchData().then(data => {
// This UI update will run before the next rendering cycle
updateUI(data);
});
});

En este ejemplo, cada clic en el botón "Obtener" llama obtener datos(). Cada operación de obtención de datos se programa como una microtarea. En función de los datos obtenidos, la actualización de la interfaz de usuario se ejecuta inmediatamente después de que se completa cada operación de obtención, antes de cualquier otra tarea de representación o bucle de eventos.

Esto garantiza que los usuarios vean los datos actualizados sin experimentar demoras debido a otras tareas en el bucle de eventos.

El uso de microtareas en escenarios como este puede evitar el bloqueo de la interfaz de usuario y proporcionar interacciones más rápidas y fluidas en su aplicación.

Implicaciones del bucle de eventos para el desarrollo web

Comprender el bucle de eventos y cómo usar sus funciones es esencial para crear aplicaciones con rendimiento y capacidad de respuesta. El bucle de eventos proporciona capacidades asíncronas y paralelas, por lo que puede manejar de manera eficiente tareas complejas en su aplicación sin comprometer la experiencia del usuario.

Node.js proporciona todo lo que necesita, incluidos los trabajadores web para lograr un mayor paralelismo fuera del hilo principal de JavaScript.