Reputation: 347
This is what the Node document said
setImmediate(callback, [arg], [...])
To schedule the "immediate" execution of callback after I/O events callbacks and before setTimeout and setInterval . Returns an immediateObject for possible use with clearImmediate(). Optionally you can also pass arguments to the callback.
However, if you run the following code you'll find the result not as expected.
var fs = require('fs');
console.time('start');
setImmediate(function () {
console.log('immediate');
console.timeEnd('start');
});
setTimeout(function() {
console.log('timeout');
console.timeEnd('start');
}, 0);
process.nextTick(function () {
console.log('nextTick');
console.timeEnd('start');
});
The output is the following, setImmediate callback just executed after setTimeout callback. So do I misunderstand something in the Node document?
nextTick
start: 2ms
timeout
start: 2ms
immediate
start: 2ms
Upvotes: 1
Views: 1233
Reputation: 3904
As Node.js doc says:
setimmediate-vs-settimeout The order in which the timers are executed will vary depending on the context in which they are called. If both are called from within the main module, then timing will be bound by the performance of the process (which can be impacted by other applications running on the machine). For example, if we run the following script which is not within an I/O cycle (i.e. the main module), the order in which the two timers are executed is non-deterministic, as it is bound by the performance of the process:
Why ?.
Every event in node.js is driven by uv_run()
function of libuv. Partial Code
int uv_run(uv_loop_t* loop, uv_run_mode mode) {
int timeout;
int r;
int ran_pending;
r = uv__loop_alive(loop);
if (!r)
uv__update_time(loop);
while (r != 0 && loop->stop_flag == 0) {
uv__update_time(loop);
uv__run_timers(loop);
ran_pending = uv__run_pending(loop);
uv__run_idle(loop);
uv__run_prepare(loop);
......
uv__io_poll(loop, timeout);
uv__run_check(loop);
uv__run_closing_handles(loop);
............
So As explained by the node.js Doc, we can match each Phase of the event loop in the code.
Timer Phase uv__run_timers(loop);
I/O Callback ran_pending = uv__run_pending(loop);
idle/ prepare uv__run_idle(loop); uv__run_prepare(loop);
poll uv__io_poll(loop, timeout);
check uv__run_check(loop);
close callbacks uv__run_closing_handles(loop);
But if we see the code closely before the loop goes to timer phase, it calls
uv__update_time(loop);
to initialize the loop time.
void uv_update_time(uv_loop_t* loop) {
uv__update_time(loop);
}
What happens is uv__update_time(loop)
calls an function uv__hrtime
UV_UNUSED(static void uv__update_time(uv_loop_t* loop)) {
/* Use a fast time source if available. We only need millisecond precision.
*/
loop->time = uv__hrtime(UV_CLOCK_FAST) / 1000000;
}
This call to uv__hrtime
is platform dependent and is cpu-time-consuming work as it makes system
call to clock_gettime. It's is impacted by other application running on the machine.
#define NANOSEC ((uint64_t) 1e9)
uint64_t uv__hrtime(uv_clocktype_t type) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (((uint64_t) ts.tv_sec) * NANOSEC + ts.tv_nsec);
}
Once this is returned Timer phase is called in the event loop.
void uv__run_timers(uv_loop_t* loop) {
...
for (;;) {
....
handle = container_of(heap_node, uv_timer_t, heap_node);
if (handle->timeout > loop->time)
break;
....
uv_timer_again(handle);
handle->timer_cb(handle);
}
}
The callback in the Timer phase is run if the current time of loop is greater than the timeout.
One more important things to note is setTimeout
when set to 0
is internally converted to 1
.
Also as the hr_time
return time in Nanoseconds, this behaviour as shown by the timeout_vs_immediate.js
becomes more explanatory now.
If the preparation before the first loop took more than 1ms
then the Timer
Phase calls the callback associated with it. If it's is less than 1ms
Event-loop continues to next phase and runs the setImmediate
callback in check phase of the loop and setTimeout
in the
next tick of the loop.
Upvotes: 3