Promise里的代碼為什么比setTimeout先執行


當瀏覽器或者Node拿到一段代碼時首先做的就是傳遞給JavaScript引擎,並且要求它去執行。

然而,執行 JavaScript 並非一錘子買賣,宿主環境當遇到一些事件時,會繼續把一段代碼傳遞給 JavaScript 引擎去執行,此外,我們可能還會提供 API 給 JavaScript 引擎,比如 setTimeout 這樣的 API,它會允許 JavaScript 在特定的時機執行。

所以,我們首先應該形成一個感性的認知:一個 JavaScript 引擎會常駐於內存中,它等待着我們(宿主)把 JavaScript 代碼或者函數傳遞給它執行。

由於我們這里講的是JavaScript 語言,我們把宿主發起的任務稱為宏觀任務,把 JavaScript 引擎發起的任務稱為微觀任務。

宏觀和微觀任務

JavaScript 引擎等待宿主環境分配宏觀任務,在操作系統中,通常等待的行為都是一個事件循環,所以在 Node 術語中,也會把這個部分稱為事件循環。我們用偽代碼來表示,大概就是:

while (TRUE) {
   r = wait();
   execute(r);
 }

我們可以看到,整個循環做的事情基本上就是反復“等待 - 執行”。當然,實際的代碼中並沒有這么簡單,還有要判斷循環是否結束、宏觀任務隊列等邏輯。

有了宏觀任務和微觀任務機制,我們就可以實現 JS 引擎級和宿主級的任務了,例如:Promise 永遠在隊列尾部添加微觀任務。setTimeout 等宿主 API,則會添加宏觀任務。在執行完一個宏觀任務后再執行后一個宏觀任務。

接下來我們介紹一下 Promise。

Promise 是 JavaScript 語言提供的一種標准化的異步管理方式,它的總體思想是,需要進行 io、等待或者其它異步操作的函數,不返回真實結果,而返回一個“承諾”,函數的調用方可以在合適的時機,選擇等待這個承諾兌現(通過 Promise 的 then 方法的回調)。(建議不是很了解promise的可以去看一下阮一峰老師的ES6標准入門

Promise 的 then 回調是一個異步的執行過程,下面我們就來研究一下 Promise 函數中的執行順序,我們來看一段代碼示例:

         

1  var r = new Promise(function(resolve, reject) { 2    console.log("a"); 3    resolve() 4  }); 5  r.then(() => console.log("c")); 6  console.log("b")

 

我們執行這段代碼后,注意輸出的順序是 a b c。在進入 console.log(“b”) 之前,毫無疑問 r 已經得到了 resolve,但是 Promise 的 resolve 始終是異步操作,所以 c 無法出現在 b 之前。

接下來我們試試跟 setTimeout 混用的 Promise。為了理解微任務始終先於宏任務,將Promise改成耗時1秒。

          

 1 setTimeout(()=>console.log("d"), 0)  2 var r = new Promise(function(resolve, reject){  3   resolve()  4 });  5 r.then(() => {  6      var begin = Date.now();  7   while(Date.now() - begin < 1000);  8   console.log("c1")  9   new Promise(function(resolve, reject){ 10      resolve() 11   }).then(() => console.log("c2")) 12 });

 

這里我們強制了 1 秒的執行耗時,這樣,我們可以確保任務 c2 是在 d 之后被添加到任務隊列。

我們可以看到,即使耗時一秒的 c1 執行完畢,再 enque 的 c2,仍然先於 d 執行了,這很好地解釋了微任務優先的原理

通過一系列的實驗,我們可以總結一下如何分析異步執行的順序:

1、首先我們分析有多少個宏任務;
2、在每個宏任務中,分析有多少個微任務;
3、根據調用次序,確定宏任務中的微任務執行次序;
4、根據宏任務的觸發規則和調用次序,確定宏任務的執行次序;(同一個宏任務下的微任務始終於這個宏任務前執行:可參考:https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/ )
5、確定整個順序。

        

1 function sleep(duration) { 2   return new Promise(function(resolve, reject) { 3     console.log("b"); 4     setTimeout(resolve,duration); 5   }) 6 } 7 console.log("a"); 8 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 老師的重學前端課程,有興趣的小伙伴也可以去了解一下。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM