# 事件循环

事件循环是一个依赖执行环境实现的语言执行特性,在 node 的 libuv 和 浏览器环境内的执行逻辑是不一样的

  • Node端,microtask 在事件循环的各个阶段之间执行
  • 浏览器端,microtask 在事件循环的 macrotask 执行完之后执行

nodejs V11.0 以上 这两者之间的顺序就相同了

# 浏览器环境

主进程的代码也属于macroTask,宏任务执行完后,UI重新渲染

# macrotasks

  • script(整体代码), setImmediate, setTimeout, setInterval, I/O, UI rendering, requestAnimationFrame

# microtasks

  • process.nextTick, Promises, Object.observe, MutationObserver

# 例子

setImmediate(function () {
  console.log(1);
}, 0);
setTimeout(function () {
  console.log(2);
}, 0);
new Promise(function (resolve) {
  console.log(3);
  resolve();
  console.log(4);
}).then(function () {
  console.log(5);
});
console.log(6);
process.nextTick(function () {
  console.log(7);
});
console.log(8);
requestAnimationFrame(() => console.log(9))
// 3 4 6 8 7 5 1 2 9
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 总结

在每一次事件循环中,macrotask 只会提取一个执行,而 microtask 会一直提取,直到 microtasks 队列清空

注:一般情况下,macrotask queues 我们会直接称为 task queues,只有 microtask queues 才会特别指明

# Node 环境

之前有做过一期详细的分享,大家可以看看这篇文章 https://github.com/PeterChen1997/MyBlog/issues/6(opens new window)

简单来说,node 内的事件循环执行逻辑如下:

  • 处理到期timers的回调
  • 处理 I/O 回调
  • idle/prepare(内部函数,可以不关注)
  • 等等有没有处理完毕的事件
  • check (内部函数,此时执行setImmediate)
  • 关闭handle并调用对应的回调函数
   ┌───────────────────────┐
┌─>│        timers         │
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
│  │     I/O callbacks     │
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
│  │     idle, prepare     │
│  └──────────┬────────────┘      ┌───────────────┐
│  ┌──────────┴────────────┐      │   incoming:   │
│  │         poll          │<─────┤  connections, │
│  └──────────┬────────────┘      │   data, etc.  │
│  ┌──────────┴────────────┐      └───────────────┘
│  │        check          │
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
└──┤    close callbacks    │
   └───────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 小结

  • setTimeout 和 setImmediate 0 在 node 内的执行顺序是不一定的,但是在 io 回调内,一定是 immediately 先执行