Node生命周期

116 阅读4分钟

Node生命周期

Node 生命周期也称 Node 事件循环

  • 程序刚启动的时候,执行入口函数
  • 进入事件循环的条件
    • 别的线程里面有其他事情没有处理完
    • 有别的任务没有执行完:计时器 | 读文件
  • 如果所有的任务事情都执行完毕了,就结束 node 执行的 terminal 进入新的命令输入状态
  • 每次事件循环都要经历下面6个阶段 image.png
  • 上述每个阶段都维护一个事件队列【~=浏览器的宏任务队列,微任务队列】
  • 上述阶段队列中,需要尤其关注的是 timers 、poll、check 队列【~=浏览器的宏队列】
  • nextTick、Promise 属于微任务队列
  • 一个队列到达之后,查看这个队列里面有没有回调函数,有的话依次拿出执行,清空之后进入下一个队列

timers 队列

存放计时结束的回调函数

  • 计时器线程 检查计时器时间到达之后,将计时器回调放到这个队列

poll 队列

也称轮询队列

除了 timers、checks 的绝大部分回调都会进入该队列,例如:文件的读取、监听用户请求;

运作方式

  • 如果 poll 队列中有回调,依次执行回调,直到清空队列;
  • 如果 poll 队列中没有回调, 等待...【其他线程执行完将回调扔给其他队列】当其他队列中出现问题,结束该阶段,进入下一阶段,node 执行后的 terminal 没有其他输出也没有开启新的命令行时,一般都是在这个阶段等待,比如说server.listen
const start = Date.now();
setTimeout(function f1() {
  console.log("setTimeout", Date.now() - start);
}, 200);
const fs = require("fs");
fs.readFile("../index.js", "utf-8", (err, data) => {
  console.log("readFile");
  const start = Date.now();
  while (Date.now() - start < 300) {}
});

如果 poll 不是等待逗留模式,上述 f1 函数的运行时间间隔应该是 200ms
但是输出显示 311 【和读取文件的大小有关】
一开始 timers 队列中没有回调,主线程进入 poll 阶段 这个时候其他线程在忙着计时和读文件
poll 观察到有其他任务待执行,就会在 poll 阶段一直逗留等待
等文件读取完毕,回调进入 poll 队列 poll 将其执行完毕,执行时间需要300ms因为有while
等 poll 将读取文件的回调执行完毕, poll 观察到 timers 队列中出现新的回调,结束 poll 并随后重新进入 timers 执行回调

check 检查阶段

setTimeout 的回调会进入 timers 队列 而使用 setImmediate 的回调会直接进入check队列 因为 setImmediate 是立即执行的,没有

let i = 0;
console.time();
function test() {
  i++;
  if (i < 1000) {
    setTimeout(test, 0);
  } else {
    console.timeEnd();
  }
}
test();//14.760s
//上述 setTimeout 执行的时候不会一直停留在 timer 阶段所以会很耗时 
//改成setImmediate
let i = 0;
console.time();
function test() {
  i++;
  if (i < 1000) {
    setImmediate(test, 0);
  } else {
    console.timeEnd();
  }
}
test();//6ms

下面输出的顺序是不确定的【取决于计算机的运行环境和运行速度】,因为 setTimeout 的时间参数虽然可以写成 0,但是内部设置的时候至少是1ms的,操作系统计时是不精准的

setTimeout(() => {
  console.log("setTimeout");
}, 0);
setImmediate(() => {
  console.log("setImmediate");
});

当前阶段处在 poll 阶段的时候,由于setImediate没有计时会直接将回调放到 check 队列 所以 setImediate 会直接执行

const fs = require("fs");
fs.readFile("../index.js", (err, data) => {
  setTimeout(() => {
    console.log("setTimeout");
  }, 0);
  setImmediate(() => {
    console.log("setImmediate");
  });
});
console.log("end");

立即执行队列

nextTick、Promise 队列,不是事件循环的一部分,也没有额外的线程去实现【不会开启额外的线程】,他们会以最快的速度执行,事件循环中,每次打算执行一个回调之前,必须要清空 nextTick 和 Promise 队列
两个队列的优先级:nextTick > Promise

setImmediate(() => {
  console.log("1");
});
process.nextTick(() => {
  console.log("2");
  process.nextTick(() => {
    console.log("6");
  });
});
console.log("3");
Promise.resolve().then(() => {
  console.log("4");
  process.nextTick(() => {
    console.log("5");
  });
});
// 3 2 6 4 5 1 

先执行同步代码,再在每个回调之前检查有没有微任务

async function async1() {
  console.log("async1 start");
  await async2();//await后面的代码在then的回调里面,之前的属于同步代码
  console.log("async1 end");
}
async function async2() {
  console.log("async2");
}
console.log("script start");
setTimeout(function () {
  console.log("setTimeout0");
}, 0);
setTimeout(function () {
  console.log("setTimeout3");
}, 3);
setImmediate(() => console.log("setImmediate"));
process.nextTick(() => console.log("nextTick"));
async1();
new Promise(function (resolve) {
  console.log("promise1");
  resolve();
  console.log("promise2");
}).then(function () {
  console.log("promise3");
});
console.log("script end");
  • promise的执行函数是同步代码,同步代码执行顺序
    image.png
  • 同步函数执行完毕之后进入事件循环,各个任务中的任务如下:
nextTick: nextTick 优先级最高的立即执行任务
promises:async1 end 、promise3  立即执行任务
timers:setTimeout0、setTimeout3 
checkes:setImmediate

事件循环的执行结果是:
image.png
immediate 和setTimeout3 的执行顺序是不一定的取决于计算机的运行环境和运行速度