Nodejs 之事件循环 代码执行顺序
关于Nodejs的事件循环Event Loop,网上有各种各样的介绍,因此本文我们不再针对具体的事件循环进行说明,我们从一个实际的示例来说明Nodejs的事件循环究竟是如何工作。
背景
Nodejs 事件循环是支撑Nodejs 非阻塞IO以及异步执行的基础,因此理解事件循环的执行也就可以写出正确的代码或者说我们就能更快的找出为什么不按我写的代码顺序执行的原因所在。
说起Nodejs事件循环,不得不提一个图,介绍的是Nodejs的事件循环示意图,其中将事件循环分为:timers、pending callback、idle prepare 、poll、check、close。
其中本文我们仅关注三个阶段,timers、poll、check。
其中timers阶段对应的是setTimeout、setInterval 两个方法,而这两个方法对应的也是宏任务。
poll 是异步回调事件,除了setTimeout、setInterval、process.nextTick、Promise、setImmediate之外的事件。
check 阶段对应的是serImmediate 事件。
下面我们画一个表格来说明这三个阶段:
阶段 | 方法 | 是否宏任务 | 所属队列 |
timers | setTimeout、setIntrval | 是 | 宏任务队列 |
poll | 各种异步回调 | ||
check | setImmediate | 是 | 宏任务队列 |
另外,process.nextTick 属于微任务TickQueue队列,Promise.resolve()属于others 微任务队列。
说到这里,有小伙伴会想,说好的事件循环,咋还聊起来宏任务、微任务了,文不对题,差评。
别急,老铁,之所以会提微任务,是因为微任务也会参与Nodejs的异步执行,因此加上这俩微任务,我们就可以更明确的说明程序代码的执行顺序。
上酸菜,
示例
先简要说明下结论,微任务执行队列是按先进先出的顺序执行,但微任务队列之间是有执行顺序,nextTick queue的执行优先级大于others 微任务队列。
另外,微任务会比宏任务先执行。
示例1:如何让一个同步执行的方法延迟执行?
1 let carName; 2 function myCar() { 3 // 此处需要延迟执行 否则carName会输出undefined 4 console.log("this is mycar: " + carName); 5 } 6 7 function setCarName() { 8 carName = "Audi"; 9 }
参考做法,将myCar的执行延迟,
1 let carName; 2 function myCar() { 3 // 此处需要延迟执行 否则carName会输出undefined 4 process.nextTick(()=>{ 5 console.log("this is mycar: " + carName); 6 }); 7 setTimeout(() => { 8 console.log("this is mycar: " + carName); 9 }); 10 Promise.resolve().then(()=>{ 11 console.log("this is mycar: " + carName); 12 }); 13 14 setImmediate(()=>{ 15 console.log("this is mycar: " + carName); 16 }); 17 } 18 19 function setCarName() { 20 carName = "Audi"; 21 } 22 23 myCar(); 24 setCarName();
示例2:宏任务和微任务混合代码,在Nodejs 事件循环的加持下,会产生怎样的火花?
先说明几个原则,
1、在有同步代码执行的时候,同步代码的执行优先级会高于任何的异步代码
2、而异步代码如果分属不同的阶段,那么执行顺序也是固定
3、在同一执行背景下,微任务代码的优先级会高于宏任务代码。
简单示例1:包含单个阶段的异步代码
//同步代码优先执行 同步代码的执行优先级最高 console.log("start"); myCar(1); //会加入到事件循环timers阶段 setTimeout(() => { console.log("timeout 1"); }); //会加入到事件循环timers阶段 setTimeout(() => { console.log("timeout 2"); }); myCar(2); console.log("end"); function myCar(index) { console.log("同步输出" + index); }
通过该示例,我们会得出一个结论,在同一个事件循环阶段,会按照添加顺序,先进先出的原则进行执行。
简单示例2:加入微任务执行
加入process.NextTick及Promise 微任务队列,请注意我们前面提到,微任务队列的执行优先级高于宏任务队列,且nextTickqueue的执行优先级高于Promise所属的others。
1 //同步代码优先执行 同步代码的执行优先级最高 2 console.log("start"); 3 myCar(1); 4 5 //会加入到事件循环timers阶段 6 setTimeout(() => { 7 console.log("timeout 1"); 8 }); 9 10 //会加入到事件循环timers阶段 11 setTimeout(() => { 12 console.log("timeout 2"); 13 }); 14 15 // 会添加到微任务队列 nextTickQueue 队列中 16 process.nextTick(() => { 17 console.log("tick 1"); 18 }); 19 20 //会添加到微任务队列 others 微任务队列 21 Promise.resolve().then(() => { 22 console.log("promise 1"); 23 }); 24 25 // 会添加到微任务队列 nextTickQueue 队列中 26 process.nextTick(() => { 27 console.log("tick 2"); 28 }); 29 //会添加到微任务队列 others 微任务队列 30 Promise.resolve().then(() => { 31 console.log("promise 2"); 32 }); 33 34 myCar(2); 35 console.log("end"); 36 37 function myCar(index) { 38 console.log("同步输出" + index); 39 }
结论
本文主要描述的是Nodejs 微任务与宏任务执行的先后顺序,更多具体的执行细节,请讲上述示例手工敲代码运行之后,仔细回味,针对方法内部包含的也是同理。
1、微任务执行>宏任务任务
2、nextTick >others 执行
3、同步执行代码执行优先级最高。
4、同一阶段或同一队列的执行,按先进先出的顺序执行。
5、代码的执行和代码所在的位置无关
根据我最近的学习,总结出一个结论,那就是Nodejs 入门简单,了解原理很难,加油,少年。