跳至主要內容
Skip to content

Fetch API 完整指南:現代 HTTP 請求

Fetch API 是瀏覽器原生的現代 HTTP 請求介面,基於 Promise 設計,提供了比 XMLHttpRequest 更簡潔優雅的 API。


一、 基本用法

1.1 GET 請求

javascript
// 最簡單的用法
fetch("https://api.example.com/users")
  .then((response) => response.json())
  .then((data) => console.log(data))
  .catch((error) => console.error("Error:", error));

// async/await
async function getUsers() {
  try {
    const response = await fetch("https://api.example.com/users");
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error("Error:", error);
  }
}

1.2 POST 請求

javascript
async function createUser(userData) {
  const response = await fetch("https://api.example.com/users", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify(userData),
  });

  return response.json();
}

// 使用
const newUser = await createUser({
  name: "John",
  email: "john@example.com",
});

二、 Request 物件

2.1 完整選項

javascript
const request = new Request("https://api.example.com/users", {
  method: "POST", // GET, POST, PUT, DELETE, etc.
  headers: {
    // Headers 物件或普通物件
    "Content-Type": "application/json",
    Authorization: "Bearer token123",
  },
  body: JSON.stringify(data), // 請求 body
  mode: "cors", // cors, no-cors, same-origin
  credentials: "same-origin", // omit, same-origin, include
  cache: "default", // default, no-store, reload, etc.
  redirect: "follow", // follow, error, manual
  referrer: "about:client", // URL 或 no-referrer
  referrerPolicy: "no-referrer-when-downgrade",
  integrity: "", // Subresource Integrity
  keepalive: false, // 頁面卸載時保持請求
  signal: null, // AbortSignal
});

fetch(request);

2.2 Headers 物件

javascript
// 建立 Headers
const headers = new Headers();
headers.append("Content-Type", "application/json");
headers.append("Authorization", "Bearer token");

// 或從物件建立
const headers2 = new Headers({
  "Content-Type": "application/json",
  Authorization: "Bearer token",
});

// 操作方法
headers.set("X-Custom", "value"); // 設定(覆蓋)
headers.get("Content-Type"); // 取得
headers.has("Authorization"); // 檢查
headers.delete("X-Custom"); // 刪除

// 迭代
for (const [key, value] of headers) {
  console.log(`${key}: ${value}`);
}

三、 Response 物件

3.1 Response 屬性

javascript
const response = await fetch("/api/data");

// 狀態
console.log(response.ok); // true 如果 status 200-299
console.log(response.status); // 200
console.log(response.statusText); // "OK"

// 標頭
console.log(response.headers.get("Content-Type"));

// URL
console.log(response.url); // 最終 URL(可能經過重導向)

// 類型
console.log(response.type); // basic, cors, error, opaque

// 重導向
console.log(response.redirected); // 是否經過重導向

3.2 讀取 Body

Body 只能讀取一次!

javascript
const response = await fetch("/api/data");

// JSON
const json = await response.json();

// 文字
const text = await response.text();

// Blob(二進位)
const blob = await response.blob();

// ArrayBuffer
const buffer = await response.arrayBuffer();

// FormData
const formData = await response.formData();

3.3 克隆 Response

如果需要多次讀取:

javascript
const response = await fetch("/api/data");
const clone = response.clone();

const json = await response.json(); // 原始 response
const text = await clone.text(); // 克隆的 response

四、 錯誤處理

4.1 Fetch 的錯誤行為

WARNING

Fetch 只在網路錯誤時 reject!HTTP 錯誤狀態碼(如 404、500)不會 reject。

javascript
// ❌ 這樣不會捕捉到 404
fetch("/api/not-found")
  .then((response) => response.json()) // 即使 404 也會執行
  .catch((error) => console.error(error)); // 不會執行

// ✅ 正確做法
fetch("/api/data")
  .then((response) => {
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`);
    }
    return response.json();
  })
  .catch((error) => console.error(error));

4.2 完整錯誤處理

javascript
async function fetchWithErrorHandling(url, options = {}) {
  try {
    const response = await fetch(url, options);

    if (!response.ok) {
      // 嘗試讀取錯誤訊息
      let errorMessage;
      try {
        const errorData = await response.json();
        errorMessage = errorData.message || response.statusText;
      } catch {
        errorMessage = response.statusText;
      }

      throw new Error(`HTTP ${response.status}: ${errorMessage}`);
    }

    return response.json();
  } catch (error) {
    if (error.name === "TypeError") {
      // 網路錯誤
      throw new Error("Network error: Unable to connect");
    }
    throw error;
  }
}

五、 常見用例

5.1 FormData 上傳

javascript
const formData = new FormData();
formData.append("name", "John");
formData.append("avatar", fileInput.files[0]);

// 不要設定 Content-Type!瀏覽器會自動設定正確的 boundary
const response = await fetch("/api/upload", {
  method: "POST",
  body: formData,
});

5.2 URL 查詢參數

javascript
const params = new URLSearchParams({
  page: 1,
  limit: 10,
  search: "關鍵字",
});

const response = await fetch(`/api/users?${params}`);
// /api/users?page=1&limit=10&search=%E9%97%9C%E9%8D%B5%E5%AD%97

5.3 設定超時

Fetch 原生不支援超時,需搭配 AbortController:

javascript
async function fetchWithTimeout(url, options = {}, timeout = 5000) {
  const controller = new AbortController();
  const id = setTimeout(() => controller.abort(), timeout);

  try {
    const response = await fetch(url, {
      ...options,
      signal: controller.signal,
    });
    clearTimeout(id);
    return response;
  } catch (error) {
    clearTimeout(id);
    if (error.name === "AbortError") {
      throw new Error("Request timeout");
    }
    throw error;
  }
}

5.4 認證請求

javascript
// Bearer Token
const response = await fetch("/api/protected", {
  headers: {
    Authorization: `Bearer ${accessToken}`,
  },
});

// 攜帶 Cookie(跨域)
const response = await fetch("https://api.example.com/user", {
  credentials: "include",
});

六、 credentials 選項

控制是否傳送 Cookie:

說明
'omit'不傳送任何 Cookie
'same-origin'只有同源請求傳送(預設)
'include'總是傳送,包括跨域
javascript
// 跨域請求需要 include
fetch("https://api.other-domain.com/user", {
  credentials: "include",
});

伺服器必須回應:

http
Access-Control-Allow-Origin: https://your-domain.com
Access-Control-Allow-Credentials: true

七、 mode 選項

控制跨域行為:

說明
'cors'允許跨域請求(預設)
'same-origin'只允許同源
'no-cors'有限的跨域存取(opaque response)

7.1 no-cors 模式

javascript
// 可以發送請求,但無法讀取回應
const response = await fetch("https://other.com/image.png", {
  mode: "no-cors",
});

console.log(response.type); // "opaque"
console.log(response.ok); // false(無法判斷)
// 無法讀取 body

適用於:不需要讀取回應的場景(如埋點、預載)


八、 cache 選項

控制快取行為:

說明
'default'標準快取邏輯
'no-store'完全不使用快取
'reload'忽略快取,更新快取
'no-cache'使用快取前先驗證
'force-cache'優先使用快取
'only-if-cached'只使用快取
javascript
// 強制刷新
const response = await fetch("/api/data", {
  cache: "no-store",
});

九、 redirect 選項

控制重導向處理:

說明
'follow'自動跟隨重導向(預設)
'error'遇到重導向則報錯
'manual'手動處理重導向
javascript
const response = await fetch("/api/redirect", {
  redirect: "manual",
});

if (response.type === "opaqueredirect") {
  console.log("被重導向了");
  console.log(response.url); // 重導向目標 URL
}

十、 封裝實踐

10.1 API 客戶端封裝

javascript
class ApiClient {
  constructor(baseURL, defaultOptions = {}) {
    this.baseURL = baseURL;
    this.defaultOptions = defaultOptions;
  }

  async request(endpoint, options = {}) {
    const url = `${this.baseURL}${endpoint}`;

    const config = {
      ...this.defaultOptions,
      ...options,
      headers: {
        "Content-Type": "application/json",
        ...this.defaultOptions.headers,
        ...options.headers,
      },
    };

    if (config.body && typeof config.body === "object") {
      config.body = JSON.stringify(config.body);
    }

    const response = await fetch(url, config);

    if (!response.ok) {
      const error = await response.json().catch(() => ({}));
      throw new Error(error.message || `HTTP ${response.status}`);
    }

    return response.json();
  }

  get(endpoint, options) {
    return this.request(endpoint, { ...options, method: "GET" });
  }

  post(endpoint, body, options) {
    return this.request(endpoint, { ...options, method: "POST", body });
  }

  put(endpoint, body, options) {
    return this.request(endpoint, { ...options, method: "PUT", body });
  }

  delete(endpoint, options) {
    return this.request(endpoint, { ...options, method: "DELETE" });
  }
}

// 使用
const api = new ApiClient("https://api.example.com", {
  headers: {
    Authorization: `Bearer ${token}`,
  },
});

const users = await api.get("/users");
const newUser = await api.post("/users", { name: "John" });

總結

概念說明
fetch()返回 Promise 的現代請求 API
Request封裝請求設定
Response封裝回應資料
Headers操作 HTTP 標頭
Body多種格式:json/text/blob

> **Fetch vs XHR**:

  • Fetch 更簡潔(Promise-based)
  • Fetch 無法直接追蹤進度(需要 ReadableStream)
  • Fetch HTTP 錯誤不 reject
  • Fetch 無法取消(需搭配 AbortController)

進階挑戰

  1. 實作一個支援重試機制的 Fetch 封裝(指數退避)。
  2. 使用 Fetch 實作請求/回應攔截器。
  3. 比較 no-cors 和 cors 模式的實際行為差異。

延伸閱讀與資源