为什么React 在node环境下使用setImmediate模拟rIC

187 阅读3分钟

前言

hello,我是🥕,今天的文章简单随笔好阅读(landexie) 🌝

附上文地址【schedule 系列 - 由React引出的MessageChannel执行时机问题🌝】

在上文中我们探讨了 MessageChannel 在事件循环中的调度,顺便在 schedule 源码中看到不同运行环境中,requestIdleCallback 的模拟不一样。node|old IE 下优先使用 setImmediate,浏览器|高版本node中使用 MessageChannel,否则用 setTimeout 代替

if (typeof localSetImmediate === 'function') {
  // Node.js and old IE.
  // There's a few reasons for why we prefer setImmediate.
  //
  // Unlike MessageChannel, it doesn't prevent a Node.js process from exiting.
  // (Even though this is a DOM fork of the Scheduler, you could get here
  // with a mix of Node.js 15+, which has a MessageChannel, and jsdom.)
  // https://github.com/facebook/react/issues/20756
  //
  // But also, it runs earlier which is the semantic we want.
  // If other browsers ever implement it, it's better to use it.
  // Although both of these would be inferior to native scheduling.
  schedulePerformWorkUntilDeadline = function () {
    localSetImmediate(performWorkUntilDeadline);
  };
} else if (typeof MessageChannel !== 'undefined') {
  // DOM and Worker environments.
  // We prefer MessageChannel because of the 4ms setTimeout clamping.
  var channel = new MessageChannel();
  var port = channel.port2;
  channel.port1.onmessage = performWorkUntilDeadline;

  schedulePerformWorkUntilDeadline = function () {
    port.postMessage(null);
  };
} else {
  // We should only fallback here in non-browser environments.
  schedulePerformWorkUntilDeadline = function () {
    localSetTimeout(performWorkUntilDeadline, 0);
  };
}

不得不说优秀团队的代码可读性好到不行,webpack 和 React 中的注释都简洁明了恰到好处。在第一种情况中,注释明确说明了为什么用 setImmediate 代替 messageChannel 的原因:

  • 与 MessageChannel 不同,它不会阻止Node.js进程退出
  • setImmediate 会更早执行

尽管言简意赅,对于 node 并没有了如指掌的我还是重新复习了 node 的事件循环模型,加深了对上述注释的理解。行きましょう~

node Eventloop

回顾一下事件循环模型:

image.png

(图片来源于node官网)

timer 处于第一个阶段,setImmediate 处于 check 阶段,其他常见的 I/O 事件大多在 poll 阶段执行,MessageChannel 同样也在这个阶段。 poll 阶段的特点之一是遍历所有回调并且同步执行,直至到达硬性限制状态(hard limit)。在 Node.js 中使用 MessageChannel 时,可能会创建一个长期存在的异步操作,如果这些异步操作没有被适当地管理或关闭,可能会阻止 Node.js 进程退出,因为进程会等待所有活动的异步操作完成后再退出。

关于注释中的第二个原因,setImmediate 会更早执行,也要结合 poll 阶段的另一个特点理解:poll 队列为空且有脚本被 setImmediate() 调度时,事件循环将结束 poll 阶段,立即执行 check 阶段被setImmediate 调度的任务。同时对比 setTimeout,在同一个 I/O 循环或 timer 内 setImmediate 总是优先执行。这是因为两者同时加入各自队列情况下,setImmediate 在本次事件循环的末尾执行,而 timer 要等到下次执行。

image.png

(图片来源于node官网)

总结

别看 node 的事件循环模块这么多,先掌握 timers、poll、check 三个阶段已经可以理解大部分现象并把使用 api 做常规开发了。

本文不是干货,仅仅在 React 注释基础上加深了理解。

参考