2022-05-19 03:22:52
输出结果为:script startasync1 startasync2promise1promise2script endnextTickasync1 endpromise3setTimeout0setImmediatesetTimeout3
详细解释如下:
同步任务阶段:
代码从上到下执行,首先遇到同步任务console.log('script start'),直接输出script start。
接着执行async1函数,进入async1函数体,执行console.log('async1 start'),输出async1 start。
在async1函数中遇到await async2(),此时会先执行async2函数,进入async2函数体,执行console.log('async2'),输出async2。async2函数没有返回值且没有await,所以执行完毕后,控制权交回async1函数,但async1函数在await处暂停,继续执行后面的同步任务。
执行console.log('promise1'),输出promise1。
执行resolve()之前的同步代码console.log('promise2'),输出promise2。
执行console.log('script end'),输出script end。至此,所有同步任务执行完毕。
本轮循环(微任务队列和process.nextTick队列):
process.nextTick队列:Node执行完所有同步任务后,接下来执行process.nextTick的任务队列。在代码中,虽然没有显式地在同步任务之后立即调用process.nextTick,但在Node.js内部机制中,async函数在遇到await暂停后,其后续的微任务(如果有的话)以及process.nextTick任务会在同步任务结束后优先执行。不过在此代码中,process.nextTick的调用是在同步任务执行流程中显式定义的(虽然从代码顺序看像是在后面,但按执行逻辑,其回调会在同步任务结束后立即检查执行),这里假设其回调console.log('nextTick')在本轮循环优先执行,输出nextTick。
微任务队列:Promise对象的回调函数会进入异步任务里的“微任务”(microtask)队列。在代码中,new Promise的resolve()被调用后,其then方法的回调函数console.log('promise3')进入微任务队列。由于微任务队列追加在process.nextTick队列后面(但按照规范,process.nextTick优先级更高,先执行完nextTick任务再执行微任务),在process.nextTick任务执行完后,执行微任务队列中的任务,输出promise3。同时,async1函数中await async2()暂停后,当同步任务和nextTick、微任务(此处promise3所属的微任务是后面then的回调,而async1恢复执行也涉及微任务概念,await后的代码恢复执行是在微任务阶段)相关任务处理完后,async1函数继续执行console.log('async1 end'),此输出也属于在本轮循环微任务相关处理阶段完成(可以理解为async/await底层基于Promise,await后的代码恢复执行是微任务调度),输出async1 end。
次轮循环(定时器等宏任务队列):
timers阶段:执行setTimeout回调函数。setTimeout(function(){console.log('setTimeout0')},0)和setTimeout(function(){console.log('setTimeout3')},3)的回调函数依次被执行,但由于Node.js中setTimeout最小延迟为1毫秒,且执行顺序受系统状况和事件循环阶段影响,不过按照代码顺序和常规理解,先定义的setTimeout0回调先执行(假设此时已到其执行时间),输出setTimeout0;然后执行setTimeout3的回调,输出setTimeout3。
check阶段:执行setImmediate的回调函数console.log('setImmediate'),输出setImmediate。
关于Node.js异步任务顺序和高并发实现的理解:
异步任务顺序:
异步任务并非简单地谁快谁先完成。Node.js中异步任务有明确的分类和执行顺序。
异步任务分为追加在本轮循环的任务(如process.nextTick和Promise的回调函数)和追加在次轮循环的任务(如setTimeout、setInterval、setImmediate的回调函数)。
本轮循环的任务一定早于次轮循环的任务执行。在本轮循环中,process.nextTick队列优先于微任务队列执行。只有前一个队列全部清空以后,才会执行下一个队列。
高并发实现:
Node.js虽然是单线程的,但它基于事件循环机制实现高并发。
当产生一个事件(如I/O操作完成、定时器到期等),就会加入到该阶段对应的队列中。事件循环将该队列中的事件取出,执行之后的回调函数。
例如,在进行文件读取、网络请求等I/O操作时,Node.js不会阻塞主线程等待操作完成,而是将操作交给操作系统内核去处理,同时继续执行其他任务。当I/O操作完成后,操作系统内核会通知Node.js,Node.js将对应的回调函数加入到事件循环的相应队列中,等待执行。这样,Node.js就可以同时处理多个I/O操作,实现高并发。