1. 程式人生 > >async/await中的await小結

async/await中的await小結

由於學習Node.js寫後臺程式碼,那麼必不可少要學習一下async/await。順其自然的就找到了阮一峰大師的ES6文件,翻閱了其async這一模組內容來學習。主要來說說await吧

Await 瞭解過程

  1. 初步瞭解:
    1. 來源:阮一峰版本ES6入門文件
    2. async函式的await命令後面,可以是 Promise 物件和原始型別的值(數值、字串和布林值,但這時等同於同步操作)。
    3. await命令類似於Promise物件then的語法糖
  2. 深究:
    1. 輔助查詢:Job queue 和 event loop
    2. 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屬於什麼任務佇列。

經查詢後,

  1. bluebird的回撥操作屬於巨集任務,網上查閱非官網資料說是基於setTimeout來進行設計的(有興趣可以查詢更多的資料進行考證)。
  2. 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等待未結束 

第一次事件迴圈:

  1. 將微佇列中第一個任務拿出,打印出"test is 2" ,並將第一個promise的第二個then操作產生一個微任務,加入微任務佇列最後
  2. 將微任務佇列中第二個任務拿出,列印" native is 2"
  3. 將微任務佇列中的第三個任務拿出,await等待結束,列印"END",非同步方法test執行結束
  4. 將最後一個微任務拿出,列印" test is 3" 

這樣解釋就通了,為了理清這些內容,向群裡的朋友們請教,網上也查了寫資料,但是發現大多數的帖子的內容都是大同小異,所以將自己的一些發現寫做部落格,希望可以給和我一樣遇到這個問題而不解的朋友做個參考。

以上內容,如有不對之處,請幫忙糾正,謝謝!