關於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