加入收藏 | 设为首页 | 会员中心 | 我要投稿 宿州站长网 (https://www.0557zz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 编程 > 正文

Node.js的事件循环如何实现?这篇带你明白

发布时间:2022-02-08 15:31:14 所属栏目:编程 来源:互联网
导读:这篇文章给大家分享的是Node.js的事件循环的实现,对于事件循环的六个阶文中都有很详细的介绍,下文还有示例可以参考,对大家学习和理解Node.js的事件循环会有帮助,有需要的朋友可以了解看看这篇,接下来就跟随小编一起学习一下吧。 Node.js是单线程的语言

    拿 setTimeout 和 setImmediate 对比,这是一个常见的例子,基于被调用的时机和定时器可能会受到计算机上其它正在运行的应用程序影响,它们的输出顺序,不总是固定的。
 
setTimeout(() => log('setTimeout'));
setImmediate(() => log('setImmediate'));
 
// 第一次运行
setTimeout
setImmediate
 
// 第二次运行
setImmediate
setTimeout
    setTimeout VS setImmediate VS fs.readFile
    但是一旦把这两个函数放入一个 I/O 循环内调用,setImmediate 将总是会被优先调用。因为 setImmediate 属于 check 阶段,在事件循环中总是在 poll 阶段结束后运行,这个顺序是确定的。
 
fs.readFile(__filename, () => {
  setTimeout(() => log('setTimeout'));
  setImmediate(() => log('setImmediate'));
})
    close callbacks
    在 Libuv 中,如果调用关闭句柄 uv_close(),它将调用关闭回调,也就是事件循环的最后一个阶段 close callbacks。
 
    这个阶段的工作更像是做一些清理工作,例如,当调用 socket.destroy(),'close' 事件将在这个阶段发出,事件循环在执行完这个阶段队列里的回调函数后,检查循环是否还 alive,如果为 no 退出,否则继续下一次新的事件循环。
 
    包含 Microtask 的事件循环流程图
 
    在浏览器的事件循环中,把任务划分为 Task、Microtask,在 Node.js 中是按照阶段划分的,上面我们介绍了 Node.js 事件循环的 6 个阶段,给用户使用的主要是 timer、poll、check、close callback 四个阶段,剩下两个由系统内部调度。这些阶段所产生的任务,我们可以看做 Task 任务源,也就是常说的 “Macrotask 宏任务”。
 
    通常我们在谈论一个事件循环时还会包含 Microtask,Node.js 里的微任务有 Promise、还有一个也许很少关注的函数 queueMicrotask,它是在 Node.js v11.0.0 之后被实现的,参见 PR/22951。
 
    Node.js 中的事件循环在每一个阶段执行后,都会检查微任务队列中是否有待执行的任务。
 
 
 
    Node.js 11.x 前后差异
    Node.js 在 v11.x 前后,每个阶段如果即存在可执行的 Task 又存在 Microtask 时,会有一些差异,先看一段代码:
 
setImmediate(() => {
  log('setImmediate1');
  Promise.resolve('Promise microtask 1')
    .then(log);
});
setImmediate(() => {
  log('setImmediate2');
  Promise.resolve('Promise microtask 2')
    .then(log);
});
    在 Node.js v11.x 之前,当前阶段如果存在多个可执行的 Task,先执行完毕,再开始执行微任务。基于 v10.22.1 版本运行结果如下:
 
setImmediate1
setImmediate2
Promise microtask 1
Promise microtask 2
    在 Node.js v11.x 之后,当前阶段如果存在多个可执行的 Task,先取出一个 Task 执行,并清空对应的微任务队列,再次取出下一个可执行的任务,继续执行。基于 v14.15.0 版本运行结果如下:
 
setImmediate1
Promise microtask 1
setImmediate2
Promise microtask 2
    在 Node.js v11.x 之前的这个执行顺序问题,被认为是一个应该要修复的 Bug 在 v11.x 之后并修改了它的执行时机,和浏览器保持了一致,详细参见 issues/22257 讨论。
 
    特别的 process.nextTick()
    Node.js 中还有一个异步函数 process.nextTick(),从技术上讲它不是事件循环的一部分,它在当前操作完成后处理。如果出现递归的 process.nextTick() 调用,这将会很糟糕,它会阻断事件循环。
 
    如下例所示,展示了一个 process.nextTick() 递归调用示例,目前事件循环位于 I/O 循环内,当同步代码执行完成后 process.nextTick() 会被立即执行,它会陷入无限循环中,与同步的递归不同的是,它不会触碰 v8 最大调用堆栈限制。但是会破坏事件循环调度,setTimeout 将永远得不到执行。
 
fs.readFile(__filename, () => {
  process.nextTick(() => {
    log('nextTick');
    run();
    function run() {
      process.nextTick(() => run());
    }
  });
  log('sync run');
  setTimeout(() => log('setTimeout'));
});
 
// 输出
sync run
nextTick
    将 process.nextTick 改为 setImmediate 虽然是递归的,但它不会影响事件循环调度,setTimeout 在下一次事件循环中被执行。
 
fs.readFile(__filename, () => {
  process.nextTick(() => {
    log('nextTick');
    run();
    function run() {
      setImmediate(() => run());
    }
  });
  log('sync run');
  setTimeout(() => log('setTimeout'));
});
 
// 输出
sync run
nextTick
setTimeout
    process.nextTick 是立即执行,setImmediate 是在下一次事件循环的 check 阶段执行。但是,它们的名字着实让人费解,也许会想这两个名字交换下比较好,但它属于遗留问题,也不太可能会改变,因为这会破坏 NPM 上大部分的软件包。

(编辑:宿州站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

推荐文章
    热点阅读