相比浏览器中的事件循环, nodejs除了微任务和宏任务, 还有阶段性任务

浏览器 nodejs事件循环
运行结果
原理 基于浏览器 基于libuv
核心 宏任务、微任务 宏任务、微任务 + 阶段性任务

Node.js 事件循环,定时器和 process.nextTick()

阶段性任务流程图(6个阶段)

Node.js 官网的事件循环原理的核心流程图。

image.png

1. timers , setTimeout/setInterval回调

2. pedding callbacks 系统回调

4. poll (复杂) 处理I/O

异步I/O

  • 网络I/O
  • 文件I/O

    5. check, setImmediate回调

    6. close callbacks 关闭回调函数

1. 事件循环的起点?

Node.js 事件循环的发起点有 4 个:

  • Node.js 启动后;
  • setTimeout 回调函数;
  • setInterval 回调函数;
  • 也可能是一次 I/O 后的回调函数。

2. 循环的是什么任务呢?

事件循环的主要包含微任务和宏任务

image.png
执行阶段主要处理三个核心逻辑:

  1. 同步代码。
  2. 将异步任务插入到微任务队列或者宏任务队列中。
  3. 执行微任务或者宏任务的回调函数。


3. 循环的任务是否存在优先级概念?

微任务在事件循环中优先级是最高的;优先将微任务队列清空,再执行宏任务队列

nodejs中的微任务:

  • process.nextTick
  • promise

优先级: process.nextTick > promise

nodejs中的宏任务:

  • setTimeout
  • setInterval
  • setImmediate
  • I/O

4. 什么进程或者线程来执行这个循环?

主要还是主线程来循环遍历当前事件

所以: 主线程会因为回调函数的执行而被阻塞**

5. 无限循环有没有终点?

当所有的微任务和宏任务都清空的时候,虽然当前没有任务可执行了,但是也并不能代表循环结束了。因为可能存在当前还未回调的异步 I/O,所以这个循环是没有终点的,只要进程在,并且有新的任务存在,就会去执行。**

6. Node.js 是单线程的还是多线程的?

主线程是单线程执行的,但是 Node.js 存在多线程执行,多线程包括 setTimeout 和异步 I/O 事件。其实 Node.js 还存在其他的线程,包括垃圾回收、内存优化等

作业题:

执行结果/nodejs中事件循环?

优先级

执行1

  1. const fs = require('fs');
  2. setTimeout(() => {
  3. // 新的事件循环的起点
  4. console.log('1');
  5. }, 0);
  6. setImmediate(() => {
  7. console.log('setImmediate 1');
  8. });
  9. /// 将会在 poll 阶段执行
  10. fs.readFile('./index.html', { encoding: 'utf-8' }, (err, data) => {
  11. if (err) throw err;
  12. console.log('read file success');
  13. });
  14. /// 该部分将会在首次事件循环中执行
  15. Promise.resolve().then(() => {
  16. console.log('poll callback');
  17. });
  18. // 首次事件循环执行
  19. console.log('2');
  20. // 2
  21. // poll callback
  22. // 1
  23. // setImmediate 1
  24. // read file success

分析: setImmediate 会在 setTimeout 之后输出, 原因:
setTimeout 如果不设置时间或者设置时间为 0,则会默认为 1ms;

修改setTimeout的时间为1000ms , 定时器的回调函数执行顺序发生变化

  1. // 2
  2. // poll callback
  3. // setImmediate 1
  4. // read file success
  5. // 1

执行2

  1. const fs = require('fs');
  2. // 首次事件循环执行
  3. console.log('start');
  4. /// 将会在新的事件循环中的阶段执行
  5. fs.readFile('./index.html', { encoding: 'utf-8' }, (err, data) => {
  6. if (err) throw err;
  7. console.log('read file success');
  8. });
  9. setTimeout(() => {
  10. // 新的事件循环的起点
  11. console.log('setTimeout');
  12. }, 0);
  13. /// 该部分将会在首次事件循环中执行
  14. Promise.resolve().then(() => {
  15. console.log('Promise callback');
  16. });
  17. /// 执行 process.nextTick
  18. process.nextTick(() => {
  19. console.log('nextTick callback');
  20. });
  21. // 首次事件循环执行
  22. console.log('end');
  23. // start
  24. // end
  25. // nextTick callback
  26. // Promise callback
  27. // setTimeout
  28. // read file success

分析:
微任务队列包含:Promise.resolve 和 process.nextTick,宏任务队列包含:fs.readFile 和 setTimeout;

任务嵌套

执行

  1. const fs = require('fs');
  2. setTimeout(() => {
  3. // 新的事件循环的起点
  4. console.log('1');
  5. fs.readFile('./config/test.conf', { encoding: 'utf-8' }, (err, data) => {
  6. if (err) throw err;
  7. console.log('read file sync success');
  8. });
  9. }, 0);
  10. /// 回调将会在新的事件循环之前
  11. fs.readFile('./config/test.conf', { encoding: 'utf-8' }, (err, data) => {
  12. if (err) throw err;
  13. console.log('read file success');
  14. });
  15. /// 该部分将会在首次事件循环中执行
  16. Promise.resolve().then(() => {
  17. console.log('poll callback');
  18. });
  19. // 首次事件循环执行
  20. console.log('2');
  21. // 2
  22. // poll callback
  23. // 1
  24. // read file success
  25. // read file sync success

分析:

主线程阻塞

执行

  1. const fs = require('fs');
  2. setTimeout(() => {
  3. // 新的事件循环的起点
  4. console.log('1');
  5. sleep(10000);
  6. console.log('sleep 10s');
  7. }, 0);
  8. /// 将会在新的事件循环中的 pending callbacks 阶段执行
  9. fs.readFile('./test.conf', { encoding: 'utf-8' }, (err, data) => {
  10. if (err) throw err;
  11. console.log('read file success');
  12. });
  13. console.log('2');
  14. /// 函数实现,参数 n 单位 毫秒 ;
  15. function sleep(n) {
  16. var start = new Date().getTime();
  17. while (true) {
  18. if (new Date().getTime() - start > n) {
  19. // 使用 break 实现;
  20. break;
  21. }
  22. }
  23. }
  24. // 2
  25. // 1
  26. // // 阻塞了一会儿
  27. // sleep 10s
  28. // read file success

分析:

修改setTimeout的时间为10ms , 定时器的回调函数执行顺序发生变化

  1. // 2
  2. // read file success
  3. // 1
  4. // sleep 10s