當瀏覽器或者Node拿到一段代碼時首先作的就是傳遞給JavaScript引擎,而且要求它去執行。前端
然而,執行 JavaScript 並不是一錘子買賣,宿主環境當遇到一些事件時,會繼續把一段代碼傳遞給 JavaScript 引擎去執行,此外,咱們可能還會提供 API 給 JavaScript 引擎,好比 setTimeout 這樣的 API,它會容許 JavaScript 在特定的時機執行。promise
因此,咱們首先應該造成一個感性的認知:一個 JavaScript 引擎會常駐於內存中,它等待着咱們(宿主)把 JavaScript 代碼或者函數傳遞給它執行。瀏覽器
因爲咱們這裏講的是JavaScript 語言,咱們把宿主發起的任務稱爲宏觀任務,把 JavaScript 引擎發起的任務稱爲微觀任務。異步
JavaScript 引擎等待宿主環境分配宏觀任務,在操做系統中,一般等待的行爲都是一個事件循環,因此在 Node 術語中,也會把這個部分稱爲事件循環。咱們用僞代碼來表示,大概就是:函數
while (TRUE) {spa
r = wait();操作系統
execute(r);blog
}隊列
咱們能夠看到,整個循環作的事情基本上就是反覆「等待 - 執行」。固然,實際的代碼中並無這麼簡單,還有要判斷循環是否結束、宏觀任務隊列等邏輯。事件
有了宏觀任務和微觀任務機制,咱們就能夠實現 JS 引擎級和宿主級的任務了,例如:Promise 永遠在隊列尾部添加微觀任務。setTimeout 等宿主 API,則會添加宏觀任務。在執行完一個宏觀任務後再執行後一個宏觀任務。
接下來咱們介紹一下 Promise。
Promise 是 JavaScript 語言提供的一種標準化的異步管理方式,它的整體思想是,須要進行 io、等待或者其它異步操做的函數,不返回真實結果,而返回一個「承諾」,函數的調用方能夠在合適的時機,選擇等待這個承諾兌現(經過 Promise 的 then 方法的回調)。(建議不是很瞭解promise的能夠去看一下阮一峯老師的ES6標準入門)
Promise 的 then 回調是一個異步的執行過程,下面咱們就來研究一下 Promise 函數中的執行順序,咱們來看一段代碼示例:
var r = new Promise(function(resolve, reject){
console.log("a");
resolve()
});
r.then(() => console.log("c"));
console.log("b")
咱們執行這段代碼後,注意輸出的順序是 a b c。在進入 console.log(「b」) 以前,毫無疑問 r 已經獲得了 resolve,可是 Promise 的 resolve 始終是異步操做,因此 c 沒法出如今 b 以前。
接下來咱們試試跟 setTimeout 混用的 Promise。爲了理解微任務始終先於宏任務,將Promise改爲耗時1秒。
setTimeout(()=>console.log("d"), 0)
var r = new Promise(function(resolve, reject){
resolve()
});
r.then(() => {
var begin = Date.now();
while(Date.now() - begin < 1000);
console.log("c1")
new Promise(function(resolve, reject){
resolve()
}).then(() => console.log("c2"))
});
這裏咱們強制了 1 秒的執行耗時,這樣,咱們能夠確保任務 c2 是在 d 以後被添加到任務隊列。
咱們能夠看到,即便耗時一秒的 c1 執行完畢,再 enque 的 c2,仍然先於 d 執行了,這很好地解釋了微任務優先的原理
經過一系列的實驗,咱們能夠總結一下如何分析異步執行的順序:
一、首先咱們分析有多少個宏任務;
二、在每一個宏任務中,分析有多少個微任務;
三、根據調用次序,肯定宏任務中的微任務執行次序;
四、根據宏任務的觸發規則和調用次序,肯定宏任務的執行次序;(同一個宏任務下的微任務始終於這個宏任務前執行:可參考:https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/ )
五、肯定整個順序。
function sleep(duration) {
return new Promise(function(resolve, reject) {
console.log("b");
setTimeout(resolve,duration);
})
}
console.log("a");
sleep(5000).then(()=>console.log("c"));
這是一段很是經常使用的封裝方法,利用 Promise 把 setTimeout 封裝成能夠用於異步的函數。
咱們首先來看,setTimeout 把整個代碼分割成了 2 個宏觀任務,這裏不管是 5 秒仍是 0 秒,都是同樣的。
第一個宏觀任務中,包含了前後同步執行的 console.log(「a」); 和 console.log(「b」);。
setTimeout 後,第二個宏觀任務執行調用了 resolve,而後 then 中的代碼異步獲得執行,因此調用了 console.log(「c」),最終輸出的順序纔是: a b c。
這裏應該更能瞭解Promise和setTimeout以及其餘代碼的執行順序了。
本文參考於 winter 老師的重學前端課程,有興趣的小夥伴也能夠去了解一下。