前端er,你真的會用 async 嗎?

async 異步函數 不徹底使用攻略

前言

如今已經到 8102 年的尾聲了,前端各方面的技術發展也層出不窮,VueConf TO 2018 大會 也發佈了 Vue 3.0的計劃。而在咱們(我)的平常中也常常用 Vue 來編寫一些項目。那麼,就少不了 ES6 的登場了。那麼話說回來,你真的會用 ES6 的 async 異步函數嗎?前端

一、async 介紹

先上 MDN 介紹:developer.mozilla.org/zh-CN/docs/…ios

async function 用於聲明 一個 返回 AsyncFunction 對象的異步函數。異步函數是值經過事件循環異步執行的函數,它會經過一個隱式的 Promise 返回其結果。若是你的代碼使用了異步函數,它的語法和結構更像是標準的同步函數面試

人工翻譯:async 關鍵字是用於表示一個函數裏面有異步操做的含義。它經過返回一個 Promise 對象來返回結果它的最大的特色是:經過 async / await 將異步的操做,可是寫法和結構倒是和咱們平時寫的(同步代碼)是同樣express

二、示範

// 通常咱們會把全部請求方法都定義在一個文件裏,這裏定義一個方法來模擬咱們的平常請求
function fetch() {
    axios.get('/user?ID=12345')
      .then(function (response) {
        console.log(response);
      })
      .catch(function (error) {
        console.log(error);
      });
};
// 而後在須要它的地方調用它
async function getUserInfo() {
    const info = await fetch();

    return info;
}
getUserInfo().then(info => console.log(info));

複製代碼

咱們能夠看到,整個過程很是直觀和清晰,語句語義很是明確,整個異步操做看起來就像是同步同樣。若是看完上面的流程沒有問題的話,那咱們接下來繼續深刻的瞭解一下。axios

三、async Promise setTimeout(定時器) 的結合使用狀況

接下來給你們演示一道題目,這道題是我當時面某條的面試題,估計和多人也見過,這道題很是經典並且使用場景頁很是多,研究意義很是大,那麼我在這裏就給你們分享一下。promise

求下面的輸出結果:
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')
複製代碼

這裏一共有 8 條 log 語句,先別複製到控制檯上,你們給20秒鐘的時間默唸一下輸出的順序。bash

1..2.. .. .. 20併發

我先給上正確的答案:異步

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

若是你的答案和上面的正確答案有所誤差,那麼說明你對 async / await 的理解仍是不夠深入,但願你閱讀完個人這篇文章以後能夠直面各類同步異步問題了(嘻嘻,這還不點個贊嘛)async

咱們再來回顧一下 MDN 對 async / await 的描述:

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

async 函數中可能會有 await 表達式,這會使 async 函數暫停執行,等待 Promise 的結果出來,而後恢復async函數的執行並返回解析值(resolved)。

async/await的用途是簡化使用 promises 異步調用的操做,並對一組 Promises執行某些操做。正如Promises相似於結構化回調,async/await相似於組合生成器和 promises。

await

await 操做符用於等待一個Promise 對象。它只能在異步函數 async function 中使用。

[return_value] = await expression;
複製代碼

await 表達式會暫停當前 async function 的執行,等待 Promise 處理完成。若 Promise 正常處理(fulfilled),其回調的resolve函數參數做爲 await 表達式的值,繼續執行 async function

若 Promise 處理異常(rejected),await 表達式會把 Promise 的異常緣由拋出。

另外,若是 await 操做符後的表達式的值不是一個 Promise,則返回該值自己。

其中很是重要的一句是:遇到 await 表達式時,會讓 async 函數 暫停執行,等到 await 後面的語句(Promise)狀態發生改變(resolved或者rejected)以後,再恢復 async 函數的執行(再以後 await 下面的語句),並返回解析值(Promise的值)

這麼多 Promise 相關的內容是由於async / await 是創建在 Promise 的基礎上的呀~~

而後再來回頭看咱們的題目,會發現,有點不對勁啊

async1 end
promise2
複製代碼

那是由於還有一個Promise.resolve 的點沒有考慮,這也是我中招的點

四、分析過程

  1. 定義一個異步函數 async1
  2. 定義一個異步函數 async2
  3. 打印 ‘script start’ // *1
  4. 定義一個定時器(宏任務,優先級低於微任務),在0ms 以後輸出
  5. 執行異步函數 async1
    1. 打印 'async1 start' // *2
    2. 遇到await 表達式,執行 await 後面的 async2
      1. 打印 'async2' // *3
    3. 返回一個 Promise,跳出 async1 函數體
  6. 執行 new Promise 裏的語句
    1. 打印 ‘promise1‘ // *4
    2. resolve() , 返回一個 Promise 對象,把這個 Promise 壓進隊列裏
  7. 打印 ’script end' // *5
  8. 同步棧執行完畢
  9. 回到 async1 的函數體,async2 函數沒有返回 Promise,因此把要等async2 的值 resolve,把 Promise 壓進隊列
  10. 執行 new Promise 後面的 .then,打印 ’promise2‘ // *6
  11. 回到 async1 的函數體,await 返回 Promise.resolve() ,而後打印後面的 ’async1 end‘ // *7
  12. 最後執行定時器(宏任務) setTimeout,打印 ’setTimeout‘ // *8

我對這段代碼的過程分析大體如上(若是有什麼理解不對的地方請指出),這裏有很關鍵並且是你們容易理解錯誤的點是:不少人覺得 await 會一直等待後面的表達式執行完以後纔會執行後續代碼,實際上 await 是會先執行後面的表達式,而後返回一個Promise,接着就跳出整個 async 函數來執行後面的代碼,也就是說執行到 await 的時候,會有一個 讓出線程 的操做。等後面的同步站執行完了以後,又會回到 async 函數中等待 await 表達式的返回值,若是不是一個 Promise 對象,則會有一個期待它 resolve 成爲一個 Promise對象的過程,而後繼續執行 async 函數後面的代碼,直到是一個 Promise 對象,則把這個 Promise 對象放入 Promise 隊列裏。

因此說 ,’async1 end' 和‘promise2‘ 這個不注意就會出錯的難點就是這樣

那麼如今,咱們是否是大體上對async / await 理解了呢,咱們來改一下這道題再來看看,把 async2 改造一下

async function async1(){
    console.log('async1 start')
    await async2()
    console.log('async1 end')
}
function async2(){ // 去掉了 async 關鍵字
    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')
複製代碼

此次你們能作對了嗎~

五、平常經常使用示例

上面寫了那麼多,只是爲了方便你們對於異步函數的理解,

下面給一些咱們平常開發中使用異步函數的例子。通常來講,咱們有一個業務須要分不完成,每一個步驟都是異步的,而且嚴重依賴於上一步的執行結果,稍有不慎就會進入回調地獄(callback hell)了,這種狀況下,咱們能夠用 async / await 來完成

// 好比在這裏場景,咱們提交數據的時候先斷定用戶是否有這個權限,而後再進行下一步動做
async function submitData(data) {
    const res = await getAuth(); // 獲取受權狀態
    if (res....) {
        const data = await submit(data);
    }
    toast(data.message);
}
複製代碼

這樣就能夠保證兩個操做的前後順序

或者是在 Vue 中,一些初始化的操做

async created() {
    const res = await this.init(); // 獲取列表等操做
    const list = await this.getPage(); // 分頁請求等
}
複製代碼

可是在使用過程當中,咱們會發現剛從回調地獄中解救,而後就陷入 async / await 地獄的誕生

舉一個例子:

async created() {
    const userInfo = await this.getUserInfo(); // 獲取用戶數據
    const list = await this.getNewsList(); // 獲取文章數據
}
複製代碼

表面上看,這段語法是正確的,但並非一個優秀實現,由於它把兩個沒有前後順序的一部操做強行變成同步操做了,由於這裏的代碼是一行接着一行執行的,想一下,咱們沒有必要在獲取用戶數據以後纔去獲取文章數據,它們的工做是能夠同時進行的

這裏給出一些經常使用的併發執行的實例

async created() {
    const userInfo = this.getUserInfo(); // 它們都會返回 Promise 對象
    const list = this.getNewsList();
    await userInfo;
    await list;
    // ...do something
}
// 若是有不少請求的狀況下可使用 Promise.all
async created() {
    Promise.all([this.getUserInfo(), this.getNewsList()]).then(()=> {
        // ...do something
    });
}
複製代碼

五、圖例

async 導圖

六、小結

一、異步的終極解決方案

二、看起來像同步的異步操做

三、便捷的捕獲錯誤和調試

四、支持併發執行

五、要知道避免 async / await 地獄

七、寫在最後

好了,關於async 異步函數的不徹底指南就說到這裏了,上面所說起的內容,可能也就比較淺顯的內容。建議你們熟練使用它,在平常開發中多使用多總結纔會有沉澱的效果,都是要靠本身多練,才能熟悉使用,熟能生巧!

最後,若是你們以爲我有哪裏寫錯了,寫得很差,有其它什麼建議(誇獎),很是歡迎你們補充。但願能讓你們交流意見,相互學習,一塊兒進步! 我是一名 19 的應屆新人,以上就是今天的分享,新手上路中,後續不按期周更(或者是月更哈哈),我會努力讓本身變得更優秀、寫出更好的文章,文章中有不對之處,煩請各位大神斧正。若是你以爲這篇文章對你有所幫助,請記得點贊或者品論留言哦~。

相關文章
相關標籤/搜索