废话不多说,我们继续看书吧。
事件循环和节点
Node也是单线程的,在处理eventloop上与浏览器略有不同。从API层面来说,Node增加了两个方法process.nextTick
、setImmediate
上面我们提到了,node中的事件循环是一个基于libuv的库,那么这个库的实现机制是怎样的呢?
官网对此的解释是:
- 定时器(Timer):该阶段执行
setTimeout()
和setInterval()
执行的索赔功能。 - 挂起的回调:其执行被推迟到下一次循环迭代的 I/O 回调。
- 空闲,准备:仅由系统内部使用。
- poll:检索新的I/O事件;执行I/O相关的回调(几乎所有情况下,除了关闭回调,这些回调是由
setImmediate()
中的定时器调度的),其他情况下节点会在适当的时间阻塞在这里。 - check(检查):
setImmediate()
这里执行call函数。 - 关闭回调(close callbacks):一些关闭回调,如:socket.on('close', …)。
设置超时和设置立即
显示代码:
setTimeout(() => {
console.log('timeout');
}, 0);
setImmediate(() => {
console.log('immediate');
});
在节点环境下运行结果为:
timeout
immediate
或
immediate
timeout
没想到,我们来看看官方的说法:
首先,定时器的执行顺序将根据调用它们的上下文而变化。
其次,当从主模块调用时, 进程 的时序是有限的。
如果在 I/O 循环中,则 setImmediate
始终被调用有限次。
借用博主的话来解释一下:
使用setImmediate相对于setTimeout的优点是,无论存在多少个定时器,setImmediate总是在IO周期中的每个定时器之前执行。
process.nextTick()
此 API 类似于昨天文章中的 Promise
或 MutationObserver
微任务实现。您始终可以将 放入代码块中,以确保 在下一个宏任务开始之前执行。
显示代码:
class Lib extends require('events').EventEmitter {
constructor () {
super()
this.emit('init')
}
}
const lib = new Lib()
lib.on('init', _ => {
console.log('init!')
})
上面的代码片段显示控制台从未被执行。我们来看看如何解决process.nextTick
的输出问题:
class Lib extends require('events').EventEmitter {
constructor () {
super()
process.nextTick(_ => {
this.emit('init')
})
}
}
不容易啊!当节点主线程执行完毕后,程序触发eventloop流程寻找错误的微任务,如果微任务队列不为空,则发送init事件。
浏览器可以使用Promise.resolve()来达到同样的效果。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。