setImmediate(callback,[arg],[...])
安排I / O事件回调后“立即”执行回调,并在setTimeout和setInterval 之前安排。返回一个immediateObject,可以与clearImmediate()一起使用。您也可以选择将参数传递给回调。
但是,如果您运行以下代码,您将发现结果不符合预期。
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');
});
输出如下,setImmediate回调刚刚在 setTimeout回调后执行。那么我是否误解了Node文档中的内容?
nextTick
开始:2ms
超时
开始:2ms
立即
开始:2ms
答案 0 :(得分:2)
正如Node.js doc所说:
setimmediate-vs-settimeout 执行计时器的顺序将根据调用它们的上下文而有所不同。如果从主模块中调用两者,那么时间将受到进程性能的限制(可能受到计算机上运行的其他应用程序的影响)。 例如,如果我们运行不在I / O周期内的以下脚本(即主模块),则执行两个定时器的顺序是非确定性的,因为它受进程性能的约束:
为什么?。
node.js中的每个事件都由libuv的uv_run()
函数驱动。部分代码
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);
............
所以正如node.js Doc所解释的那样,我们可以匹配代码中事件循环的每个阶段。
计时器阶段 uv__run_timers(loop);
I / O回调 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);
但是如果我们在循环进入定时器阶段之前看到代码,它会调用
uv__update_time(loop);
初始化循环时间。
void uv_update_time(uv_loop_t* loop) {
uv__update_time(loop);
}
uv__update_time(loop)
调用函数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;
}
对uv__hrtime
的此调用取决于平台,并且在制作系统时耗费大量时间
致电clock_gettime。它受到机器上运行的其他应用程序的影响。
#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);
}
返回此参数后,将在事件循环中调用计时器阶段。
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);
}
}
如果循环的当前时间大于超时,则运行定时器阶段中的回调。
需要注意的另一个重要事项是setTimeout
设置为0
时内部转换为1
。
同样作为hr_time
返回时间(以纳秒为单位),timeout_vs_immediate.js
所示的此行为现在变得更加明确。
如果第一个循环之前的准备时间超过1ms
,那么Timer
阶段将调用与之关联的回调。如果它小于1ms
事件循环继续到下一阶段,并在循环的检查阶段运行setImmediate
回调,并在setTimeout
中运行
循环的下一个滴答。