这篇文章主要讲解了“nodeJs事件循环运行代码怎么写”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“nodeJs事件循环运行代码怎么写”吧!
Nodejs运行时
JS语言是同步,阻塞,单线程的,但是nodejs不是。Nodejs由三个主要组件:
外部依赖例如 v8,libuv,crypto
提供文件和网络服务的c++模块
基于c++模块上层封装的JS库
nodejs的异步特性主要由libuv提供。libuv是跨平台的使用c语言写的库,它主要提供对异步操作的支持。
node运行时代码运行
当我们在Nodejs中执行JS代码时,是由v8引擎处理代码执行,v8包括一块内存区域(堆)和调用栈。当定义函数,变量时,从堆中分配内存,当执行代码时将函数入栈,函数返回时出栈。
当执行异步操作时,libuv将接管该任务,然后使用操作系统的异步机制运行任务。如果缺乏系统级的异步机制,就使用线程池运行任务,保证主线程不被阻塞。
Event Loop
事件循环是一个nodejs应用运行后一直存在的循环。存在着六个不同的队列,每个都存储着不同的回调。
Timer queue(定时器队列),最小堆,由setTimeout, setInterval创建
IO队列:文件、网络操作
check队列,任务由setImmediate产生,node专有
close队列, 与异步任务的close事件相关
nextTick队列
promise队列
除了两个微任务队列,其他队列都是libuv自带的
如何工作?
同步代码优于异步代码,事件循环是call stack为空后开始。事件循环遵循的优先级规则:
微任务队列有任务,先处理完。nextTick先于promise
定时器任务执行
IO队列
check队列
close队列
需要注意的是在定时器队列,IO队列,check队列,close队列执行一个任务后都会检查并运行微任务队列。
实验
实验1
// index.js console.log("console.log 1"); process.nextTick(() => console.log("this is process.nextTick 1")); console.log("console.log 2");
输出
console.log 1
console.log 2
this is process.nextTick 1
结论: 同步先于异步
实验2
// index.js Promise.resolve().then(() => console.log("this is Promise.resolve 1")); process.nextTick(() => console.log("this is process.nextTick 1"));
输出
this is process.nextTick 1
this is Promise.resolve 1
结论: nextTick先于promise
实验3
// index.js process.nextTick(() => console.log("this is process.nextTick 1")); process.nextTick(() => { console.log("this is process.nextTick 2"); process.nextTick(() => console.log("this is the inner next tick inside next tick") ); }); process.nextTick(() => console.log("this is process.nextTick 3")); Promise.resolve().then(() => console.log("this is Promise.resolve 1")); Promise.resolve().then(() => { console.log("this is Promise.resolve 2"); process.nextTick(() => console.log("this is the inner next tick inside Promise then block") ); }); Promise.resolve().then(() => console.log("this is Promise.resolve 3"));
实验3
// index.js process.nextTick(() => console.log("this is process.nextTick 1")); process.nextTick(() => { console.log("this is process.nextTick 2"); process.nextTick(() => console.log("this is the inner next tick inside next tick") ); }); process.nextTick(() => console.log("this is process.nextTick 3")); Promise.resolve().then(() => console.log("this is Promise.resolve 1")); Promise.resolve().then(() => { console.log("this is Promise.resolve 2"); process.nextTick(() => console.log("this is the inner next tick inside Promise then block") ); }); Promise.resolve().then(() => console.log("this is Promise.resolve 3"));
输出
this is process.nextTick 1
this is process.nextTick 2
this is process.nextTick 3
this is the inner next tick inside next tick
this is Promise.resolve 1
this is Promise.resolve 2
this is Promise.resolve 3
this is the inner next tick inside Promise then block
解析:
nextTick内部增加的nextTick任务还是先于promise,因为nexttick队列清完后才会执行promise队列的任务。
promise里增加的nextTick任务晚于其他的promise,因为此时是在执行promise阶段,需要清空promise才会检查nextTick队列。
实验4
// index.js setTimeout(() => console.log("this is setTimeout 1"), 0); setTimeout(() => { console.log("this is setTimeout 2"); process.nextTick(() => console.log("this is inner nextTick inside setTimeout") ); }, 0); setTimeout(() => console.log("this is setTimeout 3"), 0); process.nextTick(() => console.log("this is process.nextTick 1")); process.nextTick(() => { console.log("this is process.nextTick 2"); process.nextTick(() => console.log("this is the inner next tick inside next tick") ); }); process.nextTick(() => console.log("this is process.nextTick 3")); Promise.resolve().then(() => console.log("this is Promise.resolve 1")); Promise.resolve().then(() => { console.log("this is Promise.resolve 2"); process.nextTick(() => console.log("this is the inner next tick inside Promise then block") ); }); Promise.resolve().then(() => console.log("this is Promise.resolve 3"));
输出
this is process.nextTick 1 this is process.nextTick 2 this is process.nextTick 3 his is the inner next tick inside next tick this is Promise.resolve 1 this is Promise.resolve 2 this is Promise.resolve 3 this is the inner next tick inside Promise then block this is setTimeout 1 this is setTimeout 2 this is inner nextTick inside setTimeout this is setTimeout 3
结论:
nextTick先于promise;微任务先于setTimeout;每个Timer任务后会检查执行微任务。
实验6
// index.js setTimeout(() => console.log("this is setTimeout 1"), 1000); setTimeout(() => console.log("this is setTimeout 2"), 500); setTimeout(() => console.log("this is setTimeout 3"), 0);
输出
this is setTimeout 3
this is setTimeout 2
this is setTimeout 1
结论: Timer队列是按时间排序的
实验7
// index.js const fs = require("fs"); fs.readFile(__filename, () => { console.log("this is readFile 1"); }); process.nextTick(() => console.log("this is process.nextTick 1")); Promise.resolve().then(() => console.log("this is Promise.resolve 1"));
输出
this is process.nextTick 1
this is Promise.resolve 1
结论:微任务先于io任务
实验8
// index.js const fs = require("fs"); setTimeout(() => console.log("this is setTimeout 1"), 0); fs.readFile(__filename, () => { console.log("this is readFile 1"); });
输出
不确定
解析:setTimeout 0通常内部会取1ms,也就是1ms后执行Timer任务,而cpu进入事件循环的时机不定,所以有可能进入事件循环时已经过了1ms,那么先执行timer任务,也可能进入时定时任务没到时间,会先执行IO任务。
实验9
// index.js const fs = require("fs"); fs.readFile(__filename, () => { console.log("this is readFile 1"); }); process.nextTick(() => console.log("this is process.nextTick 1")); Promise.resolve().then(() => console.log("this is Promise.resolve 1")); setTimeout(() => console.log("this is setTimeout 1"), 0); for (let i = 0; i < 2000000000; i++) {}
输出
this is process.nextTick 1
this is Promise.resolve 1
this is setTimeout 1
this is readFile 1
解析:
代码最后加了循环保证进入事件循环时定时器任务已经到期,所以先执行Timer任务
实验10
// index.js const fs = require("fs"); fs.readFile(__filename, () => { console.log("this is readFile 1"); }); process.nextTick(() => console.log("this is process.nextTick 1")); Promise.resolve().then(() => console.log("this is Promise.resolve 1")); setTimeout(() => console.log("this is setTimeout 1"), 0); setImmediate(() => console.log("this is setImmediate 1")); for (let i = 0; i < 2000000000; i++) {}
输出
this is process.nextTick 1
this is Promise.resolve 1
this is setTimeout 1
this is setImmediate 1
this is readFile 1
解析: 按理说IO任务先于check任务,但是第一次事件循环时IO任务的callback并不在队列里。在两个队列之间会通过IO polling的方式去查看io任务是否完成,完成了就将callback加到队列里,然后下一轮循环时会调用