Web Development 網站開發 (11) - Javascript 非同步:Async and Await

Async/Await 其實就是 Promise,不過是另一種寫法。它的誕生就是要把非同步的事件處理寫成跟一般逐行執行的程式碼一樣,增加可讀性!

Async/Await 用法

第一次接觸 Async/Await 時跟一般的 Promise 用的 then 差異蠻大的,可能需要多點時間習慣。使用時會先在某個 function 的前面加上 async ,我們稱這個它為 async function。async function 回傳的值在外面看起來是一個 promise,跟 then 裡面的 function 回傳的值的概念差不多。然後 await 必須要放在 async function 裡,需要特別注意。

// 一般的 fetch 用法:
fetch("https://example.com/data.json")
    .then(res => res.json())
    .then(data => {
        console.log(data);
    });


// 改成 async 的寫法會變成這樣
// 在一個 promise 前加上 await 就可以取得它回傳的結果,看起來就像是一般的邏輯一樣。
async function main() {
    const res = await fetch("https://example.com/data.json");
    const data = await res.json();
    console.log(data);
}

main();
async/await

用了 Async/Await 的寫法後不僅可讀性更高,同時也可以更好的處理一些更複雜地非同步狀況。

// 如果你需要在 promise 的過程中加上一些條件式的判斷,大概會長這樣:
fetch("https://example.com/data.json")
    .then(res => res.json())
    .then(data => {
        if (data.length > 1) {
            return fetch("https://a.com/1.json").then(res => res.json)
        } else if (data.length === 1){
            return fetch("https://a.com/2.json").then(res => res.json)
        }
        return null;
    }).then(result => {
        // do somthing ...
    });


// 用 async await 處理這些 if else + promise 的邏輯看起來就是比較乾淨易讀。
// async/await 跟 then 是可以隨時混用的。下面的 then 回傳的東西也是 promise,所以可以套用 await。
async function main() {
    const res = await fetch("https://example.com/data.json");
    const data = await res.json();
    
    let result = null;
    if (data.length > 1) {
    	result = await fetch("https://a.com/1.json").then(res => json);
    } else {
    	result = await fetch("https://a.com/2.json").then(res => json);
    }
    // do somthing ...
}

main();

謹慎使用 Async/Await

由於非同步事件處理起來簡單了, 有時過度使用 async/await 可能會引發一些問題。

  1. async/await 會讓你的非同步任務跟一般的邏輯參雜在一起依序執行。像是 fetchsetTimeout 等之所以會是非同步是因為處理這些任務需要一小段時間,如果不是以非同步的方法去執行,很有可能它們會阻礙到你主程式的運行,因而讓你的網站用起來卡卡的。
async function main() {
    // 假設每個 fetch 要發上 1 秒,因為做了 await 讓這四個 fetch 依序執行,總共就要花上 4 秒。
    const a = await fetch("https://example.com/data1.json");
    const b = await fetch("https://example.com/data2.json");
    const c = await fetch("https://example.com/data3.json");
    const d = await fetch("https://example.com/data4.json");
    
    // 解決方式可以試試看用 Promise.all 讓他們平行處理,Promise.all 會一次處理所有 Promise,直到所有任務都完成後把結果依序放進 array 裡回傳 
    const [w, x, y, z] = await Promise.all([
        fetch("https://example.com/data1.json")
        fetch("https://example.com/data2.json")
        fetch("https://example.com/data3.json")
        fetch("https://example.com/data4.json")
    ]);
}

main();

2. 遇到一些不明所以的錯誤很難 debug,其中一個 case 就是 把 promise run 在 forEach ,不會報錯,但他就是不會依序執行。

const result = [];

// 以下 forEach 會走訪 1, 2, 3, 4 然後分別去 fetch data1, data2, data3, dat4 並把結果放在 result 裡。實際上他並不會按照順序。
[1, 2, 3, 4].forEach(async num => {
    const res = await fetch(`https://example.com/data${num}.json`);
    result.push(res);
})

// 原因是因為 forEach 的實現方式大概是長這樣:
Array.prototype.forEach = function(fn) {
    for (let i = 0; i < this.length; i++) {
        // async function 丟進來他也是正常執行而已,所以不會有你想像中的效果
        fn(this[i]); 
    }
}

// 簡單的解決方法就是不要用 forEach,可以用正常的 for loop, for in, for of 等等
for (let num of [1, 2, 3, 4]) {
    const res = await fetch(`https://example.com/data${num}.json`);
    result.push(res);
}
Huaying Tsai

Huaying Tsai

擅長 Python, Javascript, React, GraphQL。 想寫寫一些適合新手的程式語言教學文。 想推廣現代社會學習多元技能的風氣,建立了技能交換的平台 - https://thoth.tw
台灣