async/await中的await小結
由於學習Node.js寫後臺程式碼,那麼必不可少要學習一下async/await。順其自然的就找到了阮一峰大師的ES6文件,翻閱了其async這一模組內容來學習。主要來說說await吧
Await 瞭解過程
- 初步瞭解:
- 來源:阮一峰版本ES6入門文件
- async函式的await命令後面,可以是 Promise 物件和原始型別的值(數值、字串和布林值,但這時等同於同步操作)。
- await命令類似於Promise物件then的語法糖
- 深究:
- 輔助查詢:Job queue 和 event loop
- await 命令後面若不是Promise物件,但是await命令後的函式體內執行了promise會是什麼樣。
基於上面的三點的內容讓我對Await有了最初的瞭解,當然還是不太清楚的。通過JS-Bin邊列印,邊測試。還不是很清楚async/await的好處,雖然文件明明白白的寫著以同步程式碼的方式來寫非同步程式碼。沒有去理解async非同步,await等待的意思。就開始寫Node.js了。剛好我用到的資料庫處理模組是sequelize,基於bluebird的promise物件寫的。這就為我之後苦苦尋找await到底是如果判斷等待結束標誌埋下了種子。
Part.1
導火索是類似於下面的程式碼
const blueBird= require("bluebird");// bluebird物件 async function test(){ await blue(); await native(); console.log("END"); } // 原生Promise物件呼叫方法 function native(){ new Promise(r=>{r(2)}) .then(r=>{ console.log(`native:${r}`); }) } // Bluebird物件呼叫方法 function blue(){ new blueBird((r)=>{r(2);}) .then(r=>{ console.log(`blue:${r}`); }); } test();
上面程式碼執行後的結果是: native : 2 END blue : 2
首先我的疑惑是為什麼bluebird的promise的回撥會在最後執行呢?
這裡需要查詢的知識點有bluebird的非同步是如何實現的,事件迴圈與任務佇列兩部分內容,await屬於什麼任務佇列。
經查詢後,
- bluebird的回撥操作屬於巨集任務,網上查閱非官網資料說是基於setTimeout來進行設計的(有興趣可以查詢更多的資料進行考證)。
- async/await本質上是基於Promise的一種封裝,屬於微任務佇列。
基於以上兩點,能理解了為什麼blue:2一定在最後執行,因為巨集任務佇列的執行順序在微任務之後。
Part.2
現在令我疑惑的是為什麼native:2會在END之前呢?
await 後面的表示式返回的不是Promise物件,是undefined。那麼按道理native的主執行緒程式碼走完以後,就應該await等待結束了。根據我已經獲得的知識,不能解釋。按照我目前的理解是,
test開始 => blue執行 => blue中的bluebird物件回撥加入巨集任務佇列 ,執行結束 => blue返回undefined,blue結束,第一個await等待結束 => native執行 => native中的promise物件的回撥加入微任務佇列 => native返回undefined,native結束,第二個await等待結束 => 列印"END" => test結束 => 執行微佇列中的promise回撥,列印得到"native : 2" => 執行巨集任務佇列中的bluebird回撥,列印得到"blue:2"。
為什麼!?native:2 會在END之前列印呢? 不解決真的令我心裡有結。在與技術交流群中的兄弟們討論了後,終於豁然開朗了。令我這麼迷惑最根本的原因是關於await有一句關鍵的話我沒有看到:
紅色箭頭的話什麼意思?意思就是await後面如果跟的不是Promise,那麼await也會將其包裝成Promise來處理,加入promise佇列。趕緊用程式碼來驗證一下。
async function test(){
new Promise(resolve=>{resolve(2)})
.then(r=>{
console.log(`test is ${r}`);
}).then(r=>{
console.log(`test is 3`);
});
await native();
console.log("END");
}
function native(){
new Promise(resolve=>{
resolve(2);
}).then(r=>{
console.log(`native is ${r}`);
})
}
test();
和我猜想的一樣。處理的順序應該是
主執行緒:
test開始 => 第一個promise物件執行完畢,回撥加入微任務佇列 => native開始執行 => native執行結束,回撥加入微佇列 => await後面的native返回undefined,await將其包裝成promise,並加入微佇列,await等待未結束
第一次事件迴圈:
- 將微佇列中第一個任務拿出,打印出"test is 2" ,並將第一個promise的第二個then操作產生一個微任務,加入微任務佇列最後
- 將微任務佇列中第二個任務拿出,列印" native is 2"
- 將微任務佇列中的第三個任務拿出,await等待結束,列印"END",非同步方法test執行結束
- 將最後一個微任務拿出,列印" test is 3"
這樣解釋就通了,為了理清這些內容,向群裡的朋友們請教,網上也查了寫資料,但是發現大多數的帖子的內容都是大同小異,所以將自己的一些發現寫做部落格,希望可以給和我一樣遇到這個問題而不解的朋友做個參考。
以上內容,如有不對之處,請幫忙糾正,謝謝!