async await、Promise、setTimeout執行順序

由題目見真知:

【題目】

async function async1(){
   console.log('async1 start');
    await async2();
    console.log('async1 end')
}
async function async2(){
    console.log('async2')
}
console.log('script start');
setTimeout(function(){
    console.log('setTimeout')
},0);
async1();
new Promise(function(resolve){
    console.log('promise1');
    resolve();
}).then(function(){
    console.log('promise2')
});
console.log('script end')
複製代碼

答案:

script start
async1 start
async2
promise1
script end
promise2
async1 end
setTimeout
複製代碼

相關知識點:

阮一峯老師的解釋我覺得更容易理解:

async 函數返回一個 Promise 對象,當函數執行的時候,一旦遇到 await 就會先返回,等到觸發的異步操作完成,再接着執行函數體內後面的語句。 可以理解爲,是讓出了線程,跳出了async 函數體

然後,下面的這個輸出順序,和我們預想的不一樣啊

async1 end
promise2
複製代碼

==async==

async function 聲明將定義一個返回 AsyncFunction 對象的異步函數。
當調用一個 async 函數時,會返回一個 Promise 對象。
當這個 async 函數返回一個值時,Promise 的 resolve 方法會負責傳遞這個值;
當 async 函數拋出異常時,Promise 的 reject 方法也會傳遞這個異常值。

所以你現在知道咯,使用 async 定義的函數,當它被調用時,它返回的其實是一個Promise對象。

我們再來看看 await 表達式執行會返回什麼值。

await

語法:[return_value] = await expression; 表達式(express):一個 Promise 對象或者任何要等待的值。 返回值(return_value):返回 Promise 對象的處理結果。如果等待的不是 Promise 對象,則返回該值本身。

所以,當await操作符後面的表達式是一個Promise的時候,它的返回值,實際上就是Promise的回調函數resolve的參數。

明白了這兩個事情後,我還要再囉嗦兩句。我們都知道Promise是一個立即執行函數,但是他的成功(或失敗:reject)的回調函數resolve卻是一個異步執行的回調。
當執行到resolve()時,這個任務會被放入到回調隊列中,等待調用棧有空閒時事件循環再來取走它。

解題思路:

執行到 async1 這個函數時,首先會打印出“async1 start”(這個不用多說了吧,async 表達式定義的函數也是立即執行的);

然後執行到 await async2(),發現 async2 也是個 async 定義的函數,所以直接執行了“console.log('async2')”,同時async2返回了一個Promise,劃重點:此時返回的Promise會被放入到回調隊列中等待,await會讓出線程(js是單線程還用我介紹嗎),接下來就會跳出 async1函數 繼續往下執行。

然後執行到 new Promise,前面說過了promise是立即執行的,所以先打印出來“promise1”,然後執行到 resolve 的時候,resolve這個任務就被放到回調隊列中(前面都講過了上課要好好聽啊喂)等待,然後跳出Promise繼續往下執行,輸出“script end”。

接下來是重頭戲。同步的事件都循環執行完了,調用棧現在已經空出來了,那麼事件循環就會去回調隊列裏面取任務繼續放到調用棧裏面了。

這時候取到的第一個任務,就是前面 async1 放進去的Promise,執行Promise時發現又遇到了他的真命天子resolve函數,劃重點:這個resolve又會被放入任務隊列繼續等待,然後再次跳出 async1函數 繼續下一個任務。

接下來取到的下一個任務,就是前面 new Promise 放進去的 resolve回調 啦 yohoo~這個resolve被放到調用棧執行,並輸出“promise2”,然後繼續取下一個任務。

後面的事情相信你已經猜到了,沒錯調用棧再次空出來了,事件循環就取到了下一個任務:歷經千辛萬苦終於輪到的那個Promise的resolve回調!!!執行它(啥也不會打印的,因爲 async2 並沒有return東西,所以這個resolve的參數是undefined),此時 await 定義的這個 Promise 已經執行完並且返回了結果,所以可以繼續往下執行 async1函數 後面的任務了,那就是“console.log('async1 end')”。

然後同步(這裏的回調是可以算成同步,有的時候勉強成異步)的執行完了,開始執行等待隊列中的定時器,輸出 setTimeout


總結

  1. 調用棧
  2. 事件循環
  3. 任務隊列
  4. promise的回調函數執行
  5. async表達式的返回值
  6. await表達式的作用和返回值

隊列任務優先級:promise.Trick()>promise的回調>setTimeout>setImmediate

至此,該題的輸出結果分析完畢了,這類的執行結果可以用一句話總結,先執行同步代碼,遇到異步代碼就先加入隊列,然後按入隊的順序執行異步代碼,最後執行setTimeout隊列的代碼。

原文地址:https://lvdingjin.github.io/tech/2018/05/27/async-and-await.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章