Node生命周期
Node 生命周期也称 Node 事件循环
- 程序刚启动的时候,执行入口函数
- 进入事件循环的条件
- 别的线程里面有其他事情没有处理完
- 有别的任务没有执行完:计时器 | 读文件
- 如果所有的任务事情都执行完毕了,就结束 node 执行的 terminal 进入新的命令输入状态
- 每次事件循环都要经历下面6个阶段
- 上述每个阶段都维护一个事件队列【~=浏览器的宏任务队列,微任务队列】
- 上述阶段队列中,需要尤其关注的是 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的执行函数是同步代码,同步代码执行顺序
- 同步函数执行完毕之后进入事件循环,各个任务中的任务如下:
nextTick: nextTick 优先级最高的立即执行任务
promises:async1 end 、promise3 立即执行任务
timers:setTimeout0、setTimeout3
checkes:setImmediate
事件循环的执行结果是:
immediate 和setTimeout3 的执行顺序是不一定的取决于计算机的运行环境和运行速度