關於JavaScript異步編程,前文解析過了JavaScript併發模型,該併發模型基於事件循環。正巧又在Stackoverflow上回答了一個關於setTimeout與Promise執行順序相關的問題,因而總結這一知識點,與更多讀者分享,同時完善JavaScript異步編程系列文章。javascript
個人我的博客前端
咱們先看一到常見的前端面試題:java
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)
複製代碼
上例代碼執行輸出順序如何?這道題也是本文創做的源泉,其答案是:面試
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異步編程一文已經介紹過,本文再也不贅述,進行一些補充和總結:json
思考一下,JavaScript代碼是如何執行的呢?是一行一行代碼執行的嗎?固然不是,JavaScript 引擎一塊一塊地解析,執行JavaScript代碼,而非一行一行進行。在解析,執行代碼塊時,會須要有一個前期工做,如變量/函數提高,定義變量/函數。這裏所說的代碼塊,一般稱做可執行代碼(execuable code),一般包括全局代碼,函數代碼,eval執行代碼。而所作的前期工做就是建立執行上下文(execution context)。promise
每當JavaScript引擎開始執行應用程序時,都會建立一個執行上下文棧(後進先出),用以管理執行上下文。在執行一段可執行代碼時,會建立一個執行上下文,而後將其壓入棧,執行完畢便將該上下文退棧。併發
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
調用的都是異步任務,這一點是它們共同之處,也即都是經過任務隊列進行管理/調度。那麼它們有什麼區別嗎?下文繼續介紹。async
前文已經介紹了任務隊列的基礎內容和機制,可選擇查看,本文對任務隊列進行拓展介紹。JavaScript經過任務隊列管理全部異步任務,而任務隊列還能夠細分爲MacroTask Queue和MicoTask Queue兩類。
MacroTask Queue(宏任務隊列)主要包括setTimeout
, setInterval
, setImmediate
, requestAnimationFrame
, UI rendeing
, 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