Skip to content
本站總訪問量
本站訪客數 人次

使用 fetch() 進行非同步資料獲取的基礎教學

可以用上一篇異同步 - Asynchronous的概念繼續深入...

📝 TL;DR

  • fetch(url, options) 回傳 Promise,預設方法為 GET。
  • 常見步驟:送出請求 → response.ok 檢查 → 轉格式 (json()/text()) → 處理錯誤。
  • 需自行處理錯誤與逾時(搭配 Promise.raceAbortController)。

前置知識

  • HTTP 請求/回應概念、狀態碼 2xx/4xx/5xx。
  • Promise、async/await 與 try/catch。
  • 瀏覽器同源政策與 CORS 基本概念。

什麼是fetch()

想像你在一家餐廳點餐。當你告訴服務員你想要的食物時,你的請求就像是用 fetch() 向一個網路伺服器發送請求。fetch() 是一個 JavaScript 函數,它幫助我們從網路上獲取資料,就像你從餐廳獲得食物一樣。

如何使用 fetch()

使用 fetch() 很簡單。你只需要告訴它你想要去的網址(URL),然後它會幫你去那裡獲取資料。這裡有兩個範例,幫助你理解如何使用 fetch() 來打 API(應用程式介面)。

範例一:獲取魔法咒語

這個範例將從一個網站獲取魔法咒語的資料,並把它們顯示在網頁上的表格中。

html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>魔法咒語</title>
</head>
<body>
    <table border="1">
        <tr>
            <td>ID</td>
            <td>Name</td>
            <td>Description</td>
        </tr>
    </table>

    <script>
        fetch("https://hp-api.onrender.com/api/spells") // 使用 fetch() 獲取資料
            .then((res) => {
                if (!res.ok) throw new Error("網路錯誤:" + res.status);
                return res.json();
            })
            .then((data) => {
                data.forEach((spell) => {
                    const table = document.querySelector("table");
                    const row = table.insertRow();
                    const cell1 = row.insertCell(0);
                    const cell2 = row.insertCell(1);
                    const cell3 = row.insertCell(2);
                    cell1.innerHTML = spell.id; // 填入 ID
                    cell2.innerHTML = spell.name; // 填入名稱
                    cell3.innerHTML = spell.description; // 填入描述
                });
            })
            .catch((err) => console.error(err.message));
    </script>
</body>
</html>

範例二:獲取國家人口資料

這個範例將從另一個網站獲取國家的人口資料,並把它們顯示在網頁上的表格中。

html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>國家人口</title>
</head>
<body>
    <table border="1">
        <tr>
            <td>ID Nation</td>
            <td>ID Year</td>
            <td>Nation</td>
            <td>Population</td>
            <td>Slug Nation</td>
            <td>Year</td>
        </tr>
    </table>

    <script>
        fetch("https://datausa.io/api/data?drilldowns=Nation&measures=Population") // 使用 fetch() 獲取資料
                        .then((res) => {
                                if (!res.ok) throw new Error("網路錯誤:" + res.status);
                                return res.json();
                        })
                        .then((data) => {
                                data.data.forEach((nation) => {
                                        const table = document.querySelector("table");
                                        const row = table.insertRow();
                                        const cell1 = row.insertCell(0);
                                        const cell2 = row.insertCell(1);
                                        const cell3 = row.insertCell(2);
                                        const cell4 = row.insertCell(3);
                                        const cell5 = row.insertCell(4);
                                        const cell6 = row.insertCell(5);
                                        cell1.innerHTML = nation["ID Nation"]; // 填入 ID Nation
                                        cell2.innerHTML = nation["ID Year"]; // 填入 ID Year
                                        cell3.innerHTML = nation.Nation; // 填入 Nation 名稱
                                        cell4.innerHTML = nation.Population; // 填入 Population 人口數
                                        cell5.innerHTML = nation["Slug Nation"]; // 填入 Slug Nation
                                        cell6.innerHTML = nation.Year; // 填入 Year 年份
                                });
                        })
                        .catch((err) => console.error(err.message));
    </script>
</body>
</html>

進階:錯誤處理與逾時

javascript
const withTimeout = (promise, ms = 5000) =>
    Promise.race([
        promise,
        new Promise((_, reject) =>
            setTimeout(() => reject(new Error("請求逾時")), ms)
        ),
    ]);

const fetchJson = async (url) => {
    const res = await withTimeout(fetch(url));
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    return res.json();
};

fetchJson("https://hp-api.onrender.com/api/spells")
    .then((data) => console.log("spells", data.length))
    .catch((err) => console.error(err.message));

資料流視覺化

實戰練習

練習 1:GET 與錯誤提示(簡單)⭐

https://hp-api.onrender.com/api/spells,非 2xx 時在 console 顯示錯誤訊息。

💡 參考答案
javascript
fetch("https://hp-api.onrender.com/api/spells")
    .then((res) => {
        if (!res.ok) throw new Error(`HTTP ${res.status}`);
        return res.json();
    })
    .then((data) => console.log(data.length))
    .catch((err) => console.error(err.message));

練習 2:POST 送資料(簡單)⭐

https://jsonplaceholder.typicode.com/posts 送出 { title, body, userId },印出回應 id。

💡 參考答案
javascript
fetch("https://jsonplaceholder.typicode.com/posts", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ title: "demo", body: "hello", userId: 1 }),
})
    .then((res) => res.json())
    .then((data) => console.log("id", data.id));

練習 3:逾時與取消(中等)⭐⭐

對同一 API 同時實作 3 秒逾時與手動取消按鈕,取消時顯示「已取消」。

💡 參考答案與提示

提示: 使用 AbortController 傳入 signal,並用 Promise.race 做逾時。

參考答案(核心片段):

javascript
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 3000);

fetch("https://hp-api.onrender.com/api/spells", { signal: controller.signal })
    .then((res) => res.json())
    .then((data) => console.log(data.length))
    .catch((err) => {
        if (err.name === "AbortError") console.log("已取消");
        else console.error(err.message);
    })
    .finally(() => clearTimeout(timer));

延伸閱讀

FAQ

  • 為什麼要檢查 response.ok
    • fetch 對 4xx/5xx 不會拋錯,需自行檢查狀態碼。
  • json()text() 差異?
    • 取決於回應格式,API 多為 JSON;純文字或 HTML 則用 text()
  • 何時需要取消請求?
    • 使用者切頁、輸入框快速輸入導致舊結果過時,或節省流量時。

Contributors

The avatar of contributor named as lucashsu95 lucashsu95

Changelog