关于JavaScript异步编程,前文解析过了JavaScript并发模型,该并发模型基于事件循环。正好在Stackoverflow上回答了一个关于setTimeout与Promise执行顺序相关的问题,因而总结这一知识点,与更多读者分享,同时完善JavaScript异步编程系列文章。javascript
咱们先看一到常见的前端面试题:前端
var p1 = new Promise(function(resolve, reject){ resolve(1); }) setTimeout(function(){ console.log("will be executed at the top of the next Event Loop"); },0) p1.then(function(value){ console.log("p1 fulfilled"); }) setTimeout(function(){ console.log("will be executed at the bottom of the next Event Loop"); },0)
上例代码执行输出顺序如何?这道题也是本文创做的源泉,其答案是:java
p1 fulfilled will be executed at the top of the next Event Loop will be executed at the bottom of the next Event Loop
接下来展开解释输出结果缘由,看完本文应该能了解setTimeout和Promise的区别。面试
事件循环相关详细内容在JavaScript异步编程一文已经介绍过,本文再也不赘述,进行一些补充和总结:编程
思考一下,JavaScript代码是如何执行的呢?是一行一行代码执行的吗?固然不是,JavaScript 引擎一块一块地解析,执行JavaScript代码,而非一行一行进行。在解析,执行代码块时,会须要有一个前期工做,如变量/函数提高,定义变量/函数。这里所说的代码块,一般称做可执行代码(execuable code),一般包括全局代码,函数代码,eval执行代码。而所作的前期工做就是建立执行上下文(execution context)。json
每当JavaScript引擎开始执行应用程序时,都会建立一个执行上下文栈(后进先出),用以管理执行上下文。在执行一段可执行代码时,会建立一个执行上下文,而后将其压入栈,执行完毕便将该上下文退栈。promise
function funA() { console.log('funA') } function funB() { fun3A(); } function funC() { funB(); } funC();
ECStack.push(<funC> functionContext); // funC中调用funB,需建立funB执行上下文,入栈 ECStack.push(<funB> functionContext); // funB内调用funA,入栈上下文 ECStack.push(<funA> functionContext); // funA执行完毕,退栈 ECStack.pop(); // funB执行完毕,退栈 ECStack.pop(); // funC执行完毕,退栈 ECStack.pop(); // javascript继续执行后续代码
另外,全部的代码都是从全局环境开始执行,因此,必然栈底是全局执行上下文。并发
回顾JavaScript事件循环并发模型,咱们了解了setTimeout
和Promise
调用的都是异步任务,这一点是它们共同之处,也即都是经过任务队列进行管理/调度。那么它们有什么区别吗?下文继续介绍。异步
前文已经介绍了任务队列的基础内容和机制,可选择查看,本文对任务队列进行拓展介绍。JavaScript经过任务队列管理全部异步任务,而任务队列还能够细分为MacroTask Queue和MicoTask Queue两类。async
MacroTask Queue(宏任务队列)主要包括setTimeout
, setInterval
, setImmediate
, requestAnimationFrame
, NodeJS中的`I/O等。
MicroTask Queue(微任务队列)主要包括两类:
Object.observe
, MutationObserver
和NodeJs中的 process.nextTick
,不一样状态回调在同一函数体;JavaScript将异步任务分为MacroTask和MicroTask,那么它们区别何在呢?
The microTask queue is processed after callbacks as long as no other JavaScript is mid-execution, and at the end of each task. 只要没有其余JavaScript代码在执行,而且在每一个任务结束时,就会开始处理microTask队列。
须要注意的是,此处说的的每一个任务结束时
中的任务一般就是指macroTask,有一个比较特殊的任务- 脚本执行(JavaScript Run
),也是一个macroTask,会在JavaScript脚本执行时,当即将JavaScript Run
任务入栈macroTask队列。
本文内容介绍基本结束,那么前文第一个题目输出顺序是为何呢?简单解释一下:
JavaScript Run
入栈macroTask队列;JavaScript Run
;最后,咱们以一个题目再次回顾一下内容:
setTimeout(function(){ console.log("will be executed at the top of the next Event Loop") },0) var p1 = new Promise(function(resolve, reject){ setTimeout(() => { resolve(1); }, 0); }); setTimeout(function(){ console.log("will be executed at the bottom of the next Event Loop") },0) for (var i = 0; i < 100; i++) { (function(j){ p1.then(function(value){ console.log("promise then - " + j) }); })(i) }
代码输出结果是什么呢?快点确认一下吧:
will be executed at the top of the next Event Loop promise then - 0 promise then - 1 promise then - 2 ... promise then - 99 will be executed at the bottom of the next Event Loop
will be executed at the top of the next Event Loop
;promise then - 0
至promise then - 99
;will be executed at the bottom of the next Event Loop