Aquí está el código que estoy ejecutando:
async function sleep(ms) { const start = new Date(); while (new Date() - start < ms) { }; } const start = new Date(); sleep(5000).then(() => console.log("1!")); console.log(new Date() - start, "ms"); sleep(5000).then(() => console.log("2!")); console.log(new Date() - start, "ms");El resultado que esperaría es este:
1 ms (or some other small number of ms) 2 ms (or some other small number of ms) 1! 2!En cambio, lo que veo es esto:
5000 ms 10005 ms 1! 2! Estoy un poco confundido por esto. En primer lugar, ¿por qué las dos funciones de suspensión no se ejecutan de forma asíncrona? ¿Por qué no comienza la segunda llamada a dormir hasta que finaliza la primera? En segundo lugar, si la segunda llamada no comienza hasta que se completa la primera, ¿por qué es 1! impreso antes de las 2! ?
Si cambio la función sleep(ms) a lo siguiente, que se sugirió en otra pregunta de StackOverflow, el código funciona como se esperaba:
function sleep(ms) { return new Promise(res => setTimeout(res, ms)); } Si reemplazo sleep(ms) con un cálculo lento sin usar un tiempo de espera, ¿qué debo esperar?
JavaScript no es realmente multiproceso. Se basa en dos cosas para dar esa ilusión:
Las computadoras son realmente muy rápidas y generalmente completan lo que sea necesario instantáneamente hasta donde un humano puede decir.
Debido a que son rápidos y el mundo es lento, las computadoras pasan mucho tiempo esperando que suceda algo y pueden hacer algo más mientras tanto.
Básicamente, JavaScript ejecuta un subproceso hasta que finaliza o está esperando que suceda algo (por ejemplo, una llamada de red asíncrona), momento en el que JavaScript cambiará a otro subproceso, si lo hay.
Tu primera función sleep() es una espera ocupada . Consume el 100% de la CPU sin hacer nada. Más importante aún, nunca le da a otro hilo la oportunidad de ejecutarse. La solución de promesa lo hace a la manera de JavaScript: finaliza casi de inmediato (lo que le da a otros subprocesos la oportunidad de ejecutarse) y usa SetTimeout para recuperar el control (de una manera asíncrona) cuando otros subprocesos no se están ejecutando.
La razón por la que las funciones marcadas como async no se ejecutan simultáneamente con otro código es que async no hace que una función sea asíncrona (!). En su lugar, esta palabra clave marca una función que devuelve una Promesa y también habilita algo de sintaxis como await.
Esto significa que async no tiene absolutamente nada que ver con Threads, Tasks o cualquier otra primitiva de concurrencia conocida de otros lenguajes. En un programa de JavaScript, todo el código visible se ejecuta en un solo hilo en forma de bloqueo y se ejecuta hasta completarse. Esta es la razón por la que el ciclo while largo bloquea el flujo del programa.
¿Por qué setTimeout funciona como se esperaba? Porque el código que realmente se ejecuta es setTimeout y la devolución de llamada res , nada más. En segundo plano, el bucle de eventos garantiza que res se llame eventualmente, pero la maquinaria que maneja esto está oculta para el desarrollador.
Veamos en profundidad el primer caso.
Lo que sucede, paso a paso, es esto: tenga en cuenta que el código de usuario se ejecuta hasta completarse de manera ininterrumpida:
1! ) para que el bucle de eventos se ejecute pronto() => console.log("1!")() => console.log("2!") Si reemplaza el cuerpo de la función sleep() con setTimeout, notará que setTimeout en sí es rápido de ejecutar; todo lo que hace es poner en cola algo para que el ciclo de eventos lo procese más tarde y regrese. Por eso no se bloquea el flujo de ejecución.