Estoy confundido por el siguiente párrafo de la documentación de Node.js.
setImmediate()frentesetTimeout()... El orden en que se ejecutan los temporizadores variará según el contexto en el que se llamen. Si se llama a ambos desde el módulo principal, el tiempo estará limitado por el rendimiento del proceso (que puede verse afectado por otras aplicaciones que se ejecutan en la máquina).
Por ejemplo, si ejecutamos el siguiente script que no está dentro de un ciclo de E/S (es decir, el módulo principal), el orden en que se ejecutan los dos temporizadores no es determinista, ya que está sujeto al rendimiento del proceso:
Se procede a mostrar el siguiente ejemplo
// timeout_vs_immediate.js setTimeout(() => { console.log('timeout'); }, 0); setImmediate(() => { console.log('immediate'); }); $ node timeout_vs_immediate.js timeout immediate $ node timeout_vs_immediate.js immediate timeout No entiendo qué hace que el resultado no sea determinista. Dado que la fase de timers ocurre antes de la fase de check , ¿no deberían las devoluciones de llamada programadas por setTimeout ejecutarse siempre antes que las programadas por setImmediate ? No creo que el orden de las fases en el ciclo de eventos cambie debido a un cambio de contexto o algo así.
El documento también establece que
Sin embargo, si mueve las dos llamadas dentro de un ciclo de E/S, la devolución de llamada inmediata siempre se ejecuta primero:
Bien, pero ¿qué diferencia a los llamados "ciclos de E/S" del módulo principal?
Sé que hay muchas preguntas relacionadas, pero todas las respuestas simplemente establecen este hecho citando la documentación, sin explicar dónde entra en juego el no determinismo, por lo que no creo que esto sea un duplicado.
El truco real está en el constructor Timeout al que llama setTimeout y que aumenta los tiempos por debajo de 1 a 1. Por lo tanto setTimeout(fn, 0) es en realidad equivalente a setTimeout(fn, 1) .
Cuando libuv se inicializa , comienza con los temporizadores después de actualizar su reloj interno, y cuando ya ha pasado un milisegundo, activará el temporizador antes de proceder a la fase de encuesta (que es seguida por la fase setImmediate).
Otra observación interesante es que varios temporizadores también pueden ejecutarse antes y después de setImmeadiate:
setTimeout(() => console.log('timer'), 1); setTimeout(() => console.log('timer'), 1); setImmediate(() => console.log('immediate')); // can produce: // timer // immediate // timer Esto se debe a que setTimeout llama internamente a getLibuvNow , que llamará a env->GetNow() y que no solo obtiene la hora actual de libuv, sino que también la actualiza . Por lo tanto, puede suceder que el temporizador se coloque en la cola del temporizador con diferentes tiempos de vencimiento y, por lo tanto, la fase del temporizador solo recogerá algunos de ellos.
Bien, pero ¿qué diferencia a los llamados "ciclos de E/S" del módulo principal?
El módulo principal se ejecuta antes de que se inicialice libuv, mientras que la mayoría de los demás códigos se ejecutarán en la fase de sondeo del bucle libuv . Por lo tanto, la inicialización del módulo principal es seguida por la fase del temporizador, mientras que la fase de sondeo es seguida por la fase de 'controles de verificación', que, entre otras, ejecuta las devoluciones de llamada setImmediate . Por lo tanto, por lo general, en el módulo principal, los temporizadores se ejecutan antes que los inmediatos (si están vencidos) y, si se programan en las devoluciones de llamada, los inmediatos se ejecutan antes que los temporizadores.
También escribiré una respuesta para la visibilidad, ya que es una buena pregunta y la documentación del nodo es engañosa y tuve que investigar un poco para encontrar algunos recursos también...
Esta pregunta se respondió aquí (respuesta aceptada) muy bien con una prueba comparativa de rendimiento.
Pero la explicación real se puede encontrar en otra respuesta que apunta a este artículo que explica mejor el ciclo de eventos.
Además, lea esta respuesta (esta es la mejor, revisa el código interno y explica qué sección depende de la plataforma y consume mucho tiempo de la CPU y genera el no determinismo y también el hecho de que 0 se transforma en 1 internamente para setTimeout) a un problema planteado en nodejs que hace la misma pregunta que parece explicarlo aún más.
Una cosa más importante a tener en cuenta es que setTimeout cuando se establece en 0 se convierte internamente en 1.
Esta llamada a uv__hrtime depende de la plataforma y consume mucho tiempo de CPU, ya que hace una llamada del sistema a clock_gettime. Se ve afectado por otra aplicación que se ejecuta en la máquina.
Si la preparación antes del primer bucle tomó más de 1 ms, Timer Phase llama a la devolución de llamada asociada. Si es menos de 1 ms, Event-loop continúa con la siguiente fase y ejecuta la devolución de llamada setImmediate en la fase de verificación del bucle y setTimeout en el siguiente tic del bucle.