本文 AI 產出,尚未審核

JavaScript – Fetch 與網路請求(Networking)

主題:fetch + async/await


簡介

在現代前端開發中,與伺服器交換資料是最常見的需求。無論是取得部落格文章、送出使用者表單,或是與第三方 API 串接,都離不開 HTTP 請求。
傳統上,我們會使用 XMLHttpRequest 或是第三方函式庫(如 Axios)來完成這件事,但自 ES6 起,原生的 fetch API 已經成為瀏覽器的標準,提供 更直觀、可讀性更高 的語法。

然而,fetch 本身回傳的是 Promise,若直接使用 .then().catch(),程式碼仍可能因多層嵌套而變得難以閱讀。async/await 正是為了讓 非同步流程 看起來像同步程式碼一樣順暢。將兩者結合,就能寫出 簡潔、易除錯且具可維護性的網路請求程式

本篇文章將從概念說明、實作範例、常見陷阱與最佳實踐,一路帶你掌握 fetch + async/await 的完整使用方式,讓你在實務開發中得心應手。


核心概念

1. fetch 是什麼?

  • fetch(url, options) 會回傳一個 Promise,在請求成功(即收到回應)時 resolve,失敗(如網路斷線)時 reject。
  • 回傳的 Promise 解析後得到的是 Response 物件,它包含狀態碼、標頭以及實體資料(body)。
  • 要真正取得資料(JSON、文字、Blob…),必須呼叫 response.json()response.text() 等方法,這些方法同樣回傳 Promise。

2. 為什麼要搭配 async/await

  • async 關鍵字會把函式轉成 返回 Promise 的非同步函式。
  • async 函式內,使用 await 可以 暫停執行,直到 Promise 完成(resolve)或失敗(reject)。
  • 這樣的寫法讓 錯誤捕捉流程控制 更直觀:只需要 try / catch 就能一次處理所有錯誤。

3. 基本語法

async function getData(url) {
  try {
    const response = await fetch(url);          // 1. 發送請求,等待回應
    if (!response.ok) {                         // 2. 檢查 HTTP 狀態碼 (200~299)
      throw new Error(`HTTP ${response.status}`);
    }
    const data = await response.json();         // 3. 解析 JSON
    return data;                                // 4. 回傳結果(仍是 Promise)
  } catch (err) {
    console.error('取得資料失敗:', err);
    throw err;                                   // 讓呼叫端得知錯誤
  }
}

程式碼範例

以下提供 五個實用範例,從最簡單的 GET 請求,到帶有認證、上傳檔案、並行請求與取消請求的完整示範。

1️⃣ 基礎 GET 請求 + JSON 解析

// 取得 JSONPlaceholder 的文章清單
async function fetchPosts() {
  const url = 'https://jsonplaceholder.typicode.com/posts';
  const response = await fetch(url);
  if (!response.ok) throw new Error(`Error ${response.status}`);
  const posts = await response.json();
  console.log('Posts:', posts);
}
fetchPosts();

說明

  • await fetch(url) 等待伺服器回傳 Response
  • response.oktrue 時代表狀態碼在 200~299 範圍內。
  • await response.json() 會把回傳的字串自動轉成 JavaScript 物件。

2️⃣ POST 請求 + 傳送 JSON Body

async function createPost(title, body, userId) {
  const url = 'https://jsonplaceholder.typicode.com/posts';
  const payload = { title, body, userId };

  const response = await fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'   // 告訴伺服器傳送的資料型別
    },
    body: JSON.stringify(payload)          // 必須先序列化成字串
  });

  if (!response.ok) throw new Error('建立失敗');
  const result = await response.json();
  console.log('建立成功:', result);
}
createPost('新文章', '這是內容', 1);

重點

  • method 必須明確指定(預設為 GET)。
  • Content-Type: application/json 讓伺服器知道我們傳送的是 JSON。
  • JSON.stringify 必須在 body 中使用,否則會傳送 [object Object]

3️⃣ 加入認證 Header(Bearer Token)

async function fetchProtectedResource(token) {
  const url = 'https://api.example.com/user/profile';

  const response = await fetch(url, {
    headers: {
      'Authorization': `Bearer ${token}`   // 常見的 JWT 認證方式
    }
  });

  if (!response.ok) throw new Error('授權失敗');
  const profile = await response.json();
  console.log('使用者資料:', profile);
}

Tips

  • 千萬不要 把 token 直接寫在程式碼裡,應從安全的儲存機制(如 localStoragesessionStorage、或更安全的 HTTP‑Only cookie)取得。
  • 若伺服器回傳 401/403,代表 token 失效或權限不足,可在 catch 中統一導向登入頁面。

4️⃣ 並行多個請求(Promise.all + await

async function fetchDashboard() {
  const urls = [
    'https://api.example.com/stats',
    'https://api.example.com/notifications',
    'https://api.example.com/messages'
  ];

  // 同時發送三個請求,等待全部完成
  const responses = await Promise.all(urls.map(u => fetch(u)));

  // 檢查每個回應狀態
  responses.forEach(r => {
    if (!r.ok) throw new Error(`Request failed: ${r.url}`);
  });

  // 解析全部 JSON
  const [stats, notifications, messages] = await Promise.all(
    responses.map(r => r.json())
  );

  console.log({ stats, notifications, messages });
}
fetchDashboard();

說明

  • Promise.all 會在 任一 Promise 拒絕時立即拋出錯誤,適合需要所有結果才能繼續的情境。
  • 若希望 部分失敗也能取得成功的結果,可以改用 Promise.allSettled

5️⃣ 取消請求(AbortController

async function fetchWithTimeout(url, timeout = 5000) {
  const controller = new AbortController();      // 建立控制器
  const id = setTimeout(() => controller.abort(), timeout); // 超時自動 abort

  try {
    const response = await fetch(url, { signal: controller.signal });
    clearTimeout(id);                             // 請求成功,取消計時器
    if (!response.ok) throw new Error('Network error');
    return await response.json();
  } catch (err) {
    if (err.name === 'AbortError') {
      console.warn('請求已被取消(逾時)');
    } else {
      console.error('其他錯誤:', err);
    }
    throw err;
  }
}
fetchWithTimeout('https://api.example.com/slow-endpoint', 3000);

關鍵點

  • AbortController 讓你可以在任何時候呼叫 controller.abort()fetch 會拋出 AbortError
  • 常用於 超時控制使用者切換頁面時取消未完成的請求,避免不必要的資源浪費與 UI 異常。

常見陷阱與最佳實踐

陷阱 說明 解決方式
忘記 await fetch 回傳的是 Promise,若不加 await,後續程式會在未完成前就執行。 確認每個需要結果的呼叫都有 await,或使用 .then() 明確鏈接。
只檢查 catch fetch 只在 網路錯誤 時 reject,HTTP 錯誤(如 404、500)仍會 resolve。 必須自行檢查 response.okresponse.status,如範例所示。
直接使用 response.json() 多次 Response 的 body 只能讀一次,重複呼叫會拋錯。 若需要多種格式,先 clone() 或在一次讀取後自行轉換。
忘記設定 Content-Type POST/PUT 時不設定,伺服器可能無法正確解析 JSON。 headers 中加入 'Content-Type': 'application/json'
在 UI 直接呼叫 await 在 React、Vue 等框架的渲染函式中直接 await 會阻塞 UI。 把非同步邏輯搬到 useEffectmounted、或事件處理器中。
未處理跨域(CORS)問題 瀏覽器會因同源政策阻擋跨域請求。 確認伺服器有正確設定 Access-Control-Allow-Origin,或使用 代理

最佳實踐

  1. 統一錯誤處理

    • 建立一個封裝 fetch 的工具函式,統一檢查 ok、拋出自訂錯誤,讓上層只需要 try / catch
  2. 使用 AbortController

    • 為每個長時間請求加上超時或取消機制,提升使用者體驗。
  3. 緩存與重試

    • 針對不常變動的資源,可在客戶端快取(Cache-ControllocalStorage),或在失敗時自動重試(指數退避)。
  4. 分離 API 與 UI

    • 把所有網路請求寫在 service layer,讓 UI 只關心資料顯示與狀態,降低耦合度。
  5. 避免過度並行

    • 同時發送太多請求會導致瀏覽器或伺服器資源耗盡。可以使用 佇列節流(throttle)控制同時請求數量。

實際應用場景

場景 需求 fetch + async/await 的解法
使用者登入 送出帳號密碼、取得 JWT、存入安全儲存 POST 請求、await 解析回傳 token、localStorage.setItem
商品列表分頁 依頁碼載入資料、滾動載入更多 GET 請求 + AbortController 防止舊請求干擾、Promise.all 並行載入多頁
即時通知 定時輪詢或使用 SSE/WS 前的備援方案 setInterval + fetch,失敗時自動重試
檔案上傳 大檔案分段上傳、顯示上傳進度 fetch 搭配 ReadableStreamFormDataawait 監控每段回應
多語系內容 依使用者語系載入不同 JSON 檔案 Promise.allSettled 同時請求多語系檔案,失敗時回退預設語系

總結

  • fetch 為瀏覽器提供的 原生 HTTP 客戶端,回傳 Promise,讓非同步操作更具可組合性。
  • async/await 把 Promise 的鏈式寫法變成 類似同步程式碼的結構,大幅提升可讀性與除錯體驗。
  • 真正使用 fetch 時,必須自行檢查 HTTP 狀態設定正確的 Header,並妥善處理 跨域、取消請求、錯誤重試 等情境。
  • 透過 封裝、統一錯誤處理、AbortController、並行請求 等技巧,我們可以在前端應用中構建 可靠且易維護的網路層

掌握了 fetch + async/await 後,你就能在日常開發中快速完成資料取得、表單送出、檔案上傳等任務,同時保持程式碼的簡潔與可讀。未來若需要更進階的功能(如自動重試、離線快取),只要在此基礎上擴充即可,讓你的前端專案在 效能、可靠性與使用者體驗 上都更上一層樓。祝開發順利! 🚀