Introducción
JavaScript solo puede tener un hilo de ejecución. Esto significa que si solicitamos algo al servidor de forma síncrona, toda la web se detendrá hasta que llegue la respuesta.
Sin embargo, JavaScript es asíncrono gracias al event loop y la cola de tareas. Esto permite que operaciones como peticiones HTTP o temporizadores se ejecuten sin bloquear el hilo principal. Estas operaciones asíncronas son gestionadas por el navegador o el entorno de Node.js y, cuando terminan, sus callbacks se colocan en la cola para ser ejecutados cuando el hilo principal esté libre.
Event loop (Bucle de eventos)
A pesar de que JavaScript es un lenguaje monohilo (single-threaded), en JavaScript existe el Event Loop (bucle de eventos), que es un mecanismo que permite la ejecución de código asíncrono y no bloqueante.
Cola de tareas (Task Queue)
JavaScript gestiona las operaciones asíncronas utilizando varias colas de tareas, cada una con una prioridad diferente. Estas colas incluyen la cola de macrotareas y la cola de microtareas.
- Macrotareas: Son las tareas que se colocan en la cola de tareas principal. Incluyen:
setTimeout
setInterval
- Eventos del DOM (como clicks)
- Peticiones HTTP (
fetch
,XMLHttpRequest
)
- Microtareas: Se colocan en la cola de microtareas y tienen prioridad sobre las macrotareas. Esto significa que después de cada macrotarea, el motor de JavaScript procesará todas las microtareas antes de continuar con la siguiente macrotarea. Incluyen:
- Promesas (
.then
,.catch
,.finally
)
- Promesas (
Ejemplo de cómo retrasar un bloqueo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(() => {
let start = Date.now();
function count() {
// Trabajo pesado
let i = 0;
for (let j = 0; j < 1e9; j++) {
i++;
}
console.log("Done in " + (Date.now() - start) + 'ms');
}
//count(); // Esto bloquea el navegador
setTimeout(count, 0);
})();
- La función count ejecuta una operación costosa (una larga iteración de bucle).
- Si count se ejecuta directamente, bloquea el navegador porque el bucle es muy largo.
- Utilizando
setTimeout(count, 0)
, la función count se coloca en la cola de macrotareas, permitiendo que el navegador procese otras tareas mientras tanto
Este ejemplo ilustra cómo retrasar el bloqueo hasta que, por ejemplo, se ejecute todo el programa principal. Pero no soluciona el hecho de que, al final se va a quedar el navegador bloqueado, ya que se ejecutará en el único hilo de ejecución de Javascript.
Ejemplo de cómo dividir el trabajo
En el siguiente ejemplo se ve cómo dividir el trabajo para que, en medio, dé tiempo a renderizar o ejecutar otras tareas y microtareas como atender eventos:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
document.addEventListener("DOMContentLoaded", () => {
let progress = document.querySelector("#progress");
let i = 0;
function count() {
// Realiza una parte del trabajo pesado (*)
do {
i++;
progress.innerHTML = i;
} while (i % 1e3 != 0);
if (i < 1e7) {
setTimeout(count);
}
}
count();
});
- En lugar de hacer todo el trabajo pesado en una única ejecución, el trabajo se divide en trozos más pequeños.
- La función
count
realiza una pequeña parte del trabajo (incrementari
y actualizar el texto deprogress
) antes de ceder el control al navegador consetTimeout(count)
. - Esto permite que el navegador renderice el cambio en el DOM, evitando el bloqueo y ofreciendo una experiencia de usuario más fluida.
(Voluntario) Lee el artículo https://www.bbss.dev/posts/eventloop/ con otro ejemplo.
Ejemplo de micro y macro tareas con promesas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
(() => {
const messages = [];
messages.push('Script start');
setTimeout(() => {
messages.push('SetTimeout');
printMessages();
}, 0);
Promise.resolve().then(() => {
messages.push('Promise 1');
}).then(() => {
messages.push('Promise 2');
});
messages.push('Script end');
function printMessages() {
console.log(messages.join('\n'));
}
})();
- El log ‘Script start’ se imprime primero porque es código síncrono.
setTimeout
coloca su función de callback en la cola de macrotareas.- La promesa
Promesa
se resuelve inmediatamente, colocando sus callbacks en la cola de microtareas. - El log ‘Script end’ se imprime porque es código síncrono.
- Las microtareas (las promesas) se ejecutan antes de la macrotarea (
setTimeout
), imprimiendo ‘Promise 1’ y ‘Promise 2’. - Finalmente, la función de
setTimeout
se ejecuta, imprimiendo ‘SetTimeout’.