iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 25
2
Modern Web

從0.5開始的JavaScript系列 第 25

Day25 優雅的 Await、Async

  • 分享至 

  • twitterImage
  •  

昨日介紹完了 Promise,今日要來介紹 ES7 的 awaitasync,但是進入主題之前,我們先來想想 Promise 為了改善非同步的處理,它做了什麼事情。

先從最根本的原因開始,JS 是屬於同步執行,但是遇到費時的操作,勢必得採取非同步處理,不然就會變成這樣:

如果 AJAX 是採取同步執行,那麼在取回資料前,接下來的程式都不會接著執行,除非 AJAX 完成,這也造成了整個程式堵塞住。

myAjax()
doOtherThing()

所以為了避免堵塞,將這些操作都使用非同步處理,但是問題來了,既然這些操作被拉到一旁執行,我們就需要知道它們何時執行完畢,因為我們可能會對它進行後續處理,像是這樣:

myAjax("one.com", function(val){
    console.log(val);
})

我們用一個 callback function 來顯示 AJAX 收到的資料,目前為止看似沒問題,但是如果改成這樣呢?

我們需要顯示第 3 個來源的資料,但是下一個來源都需要前一個來源的值:

myAjax("one.com", function(val){
    myAjax("two.com", val, function(val2){
        myAjax("three.com", val2, function(val3){
            console.log(val3);
        })
    })
})

也就是我需要先取得 val,再用它來取得 val2 ,然後再用 val2 來取得 val3,這樣不斷嵌套下去,更別提每個 callback function 裡面滿滿的邏輯運算與 error 處理,程式碼變成逆時針倒 90 度的深 V,這是 ES5 以前所有開發者的噩夢。

直到 Promise 的出現,簡潔乾淨的寫法,配上一目瞭然的 thencatch,讓 callback function 處理起來優雅多了XD

myAjax("one.com")
.then(val => myAjax("two.com", val))
.then(val2 => myAjax("three.com", val2))
.then(val3 => { console.log(val3); })

至於為什麼可以使用 thencatch 來拯救世界呢?

因為 Promise 的機制,它必須要給使用者有一個「回應」,而這個「回應」就只有兩種可能

  1. 成功: resolve
  2. 失敗: reject

所以 then 負責接收 resolvecatch 負責處理 reject

而且也不是一定要等前一個 Promise 回應後才能繼續下去,只要不是範例中需要前一個來源回應後,才能接下去執行的狀況,那更是可以使用 Promise.allPromise.race 來針對不同情境,提供更方便快速的操作。

若是以上有些地方不太清楚,可以回顧

Day5 單執行緒&非同步發生的血案
Day21 AJAX(1): 科普 & XHR
Day23 Promise 詳解(1/2)
Day24 Promise 詳解(2/2)

我們的系列文可是從基礎打起又不失實用

我的用心,您看的見
/images/emoticon/emoticon08.gif


講了那麼多,趕緊進入業配主題: awaitasync

既然 Promise 那麼方便,為何還需要它們兩個呢,而且它們又是什麼東西...?

先講結論:

  1. awaitasync 也是基於 Promise 的應用,所以不是不相干的東西。
  2. 它們提供了更接近同步執行的撰寫邏輯。
  3. 或許又多了那麼一點優雅(?

await

我們都知道,非同步執行的程式碼,不管 callback function 多快執行,都無法在下一行的程式碼之前執行,像是這樣:

以下範例博君一笑,請勿認真 ^.<

setTimeout(() => { console.log("我的手速練到極致") }, 0);

console.log("我爸是連X");

請問這樣印出的順序是什麼?

如果有好好複習以前的文章,應該不會答錯了。

答案是

我爸是連X

我的手速練到極致

如果想要讓 我爸是連X我的手速練到極致 後面才印出,就要改成這樣,

setTimeout(() => {
    console.log("我的手速練到極致");
    console.log("我爸是連X");
}, 0);

讓它在非同步操作結束後才執行,也就是達到我們原本預期的執行順序。

以上狀況常常發生,我們總是要將程式碼改寫為非同步執行的邏輯

像是最簡單的取得資料:

getData()
.then(val => {
    console.log(val);
});
  1. 我們需要把顯示資料的這個動作寫在 callback function 中(廢話,沒取得資料要怎麼顯示),但是這樣卻不是同步執行的邏輯,也就是它沒辦法一步一步的往下執行。
  2. 範例中只是單純要取得資料而已,卻還是需要一個 callback function,儘管很簡潔了,但還是有點麻煩。

使用 await 能夠解決這個問題。

await 能夠在 Promise 回應狀態前,停止往下執行,所以上方的範例可以寫成這樣:

async function outer() {
    var val = await getData();
    console.log(val);
}

outer();

加了 awaitgetData ,在回應前,function outer 中的程式將不會往下執行,所以我們的 console.log(val) 也不需要再弄一個 then,然後在 callback function 中才能取得資料印出了,好像又那麼優雅了一點XD

還是不清楚嗎,我們再舉回一開始的多站點例子。

此時我們可以使用更簡潔的方法來撰寫這個範例

myAjax("one.com")
.then(val => myAjax("two.com", val))
.then(val2 => myAjax("three.com", val2))
.then(val3 => { console.log(val3); })

可以改成

async function outer() {
    var val = await myAjax("one.com");
    var val2 = await myAjax("two.com", val);
    var val3 = await myAjax("three.com", val2);

    console.log(val3);
}

outer();

甚至寫得更簡短

(async () => {
    var val2 = await myAjax("two.com", await myAjax("one.com"));
    console.log(await myAjax("three.com", val2));
})()

簡而言之

await:

  1. 對於 非同步 處理更加細膩。
  2. await 將會回傳接在它後面的 Promise 狀態值(resolve)。
  3. await 在收到 Promise 回應前,會暫停執行後續的程式。

But

await 特性的第 3 點是個大麻煩,如果 Promise 狀態回傳錯誤(reject),將導致後面的程式永遠卡住,不會再執行...

async

所以需要搭配 async,也就是寫在上方範例中 function 前面的 async

async 會像 Promise 一樣回傳狀態(成功/失敗),所以能用 thencatch 來處理。

所以說的更精準一點,await 暫停執行的是包住它、帶有 async 的 function 之內容。

處理 reject

所以我們可以在發生錯誤時,將錯誤拋出,程式碼可以寫成這樣,就和 Promise 的用法一樣,使用 catch 來處理例外/錯誤狀況。

async function outer() {
    var val = await myAjax("one.com");
    var val2 = await myAjax("two.com", val);
    var val3 = await myAjax("three.com", val2);

    console.log(val3);
}

outer()
.then(...)
.catch(error => { console.log(error); })

或是用傳統的 try catch 來處理錯誤:

async function outer() {
    try {
        var val = await myAjax("one.com");
        // 以下省略...
    }
    catch (error) {
        console.log(error);
    }
}

outer()

resolve 回傳值

async function 既然像 Promise,它的狀態除了 reject 還會有 resolve,而 resolve 的值就是函數自己的回傳值。

var outer = async () => {
    var val = await myAjax("one.com");
    ...
    return 123;
}

outer()
.then(val => {
    console.log(val); // 123
})

看完是不是覺得 Promise 如果沒有搭配 awaitasync 好像少了那麼一點優雅呢XD

今日的分享就到這,我們明天見/images/emoticon/emoticon51.gif


上一篇
Day24 Promise 詳解(2/2)
下一篇
Day26 前端福音(1/4): Firebase-介紹 & 建立專案
系列文
從0.5開始的JavaScript30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言