fetch() 基本用法
簡介
在前端開發中,與後端或第三方 API 交換資料是最常見的需求之一。過去常用 XMLHttpRequest(簡稱 XHR)來發送 HTTP 請求,但語法冗長、錯誤處理不直觀,讓人望而卻步。ES6 之後,瀏覽器原生提供了 fetch(),它以 Promise 為基礎,讓非同步網路請求變得簡潔且易於閱讀。掌握 fetch() 的基本用法,不僅能提升開發效率,還能在 React、Vue、Angular 等框架中無縫整合,建立更具可維護性的資料流。
本篇文章針對 JavaScript 初學者至中級開發者,從概念說明、實作範例、常見陷阱到最佳實踐,完整呈現 fetch() 的核心功能與實務應用,幫助你快速上手並避免常見錯誤。
核心概念
1. fetch 的基本語法
fetch() 接收兩個參數:
fetch(url, options);
url:要請求的資源路徑(字串或Request物件)。options(可選):設定 HTTP 方法、標頭、主體等資訊的物件。
fetch() 會回傳一個 Promise,解析成功時得到 Response 物件,失敗(例如網路斷線)則直接走到 catch。
2. Response 物件與資料格式
Response 內建多種方法,用於把回傳的原始位元組(body)轉成開發者需要的型別:
| 方法 | 說明 |
|---|---|
response.text() |
取得純文字 |
response.json() |
解析 JSON(最常用) |
response.blob() |
取得二進位大檔案(如圖像) |
response.arrayBuffer() |
取得原始位元組陣列 |
這些方法本身也會回傳 Promise,因此常會出現 兩層 then 或使用 async/await 讓程式碼更平順。
3. 常用的 HTTP 方法與 options
fetch() 預設使用 GET 方法。如果要傳送資料(如 POST、PUT、DELETE),必須在 options 中明確指定:
{
method: 'POST', // HTTP 方法
headers: { // 請求標頭
'Content-Type': 'application/json'
},
body: JSON.stringify(data) // 主體資料(字串化)
}
⚠️ 注意:
body必須是字串、Blob、FormData、URLSearchParams或ReadableStream,不能直接傳物件。
程式碼範例
下面提供 5 個實用範例,從最簡單的 GET 請求到進階的錯誤處理與自訂逾時機制,全部配合詳細註解。
範例 1:最簡單的 GET 請求
// 取得公開的 JSON 資料
fetch('https://api.github.com/users/octocat')
.then(response => {
// 確認 HTTP 狀態碼是否為 2xx
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json(); // 解析成 JSON 物件
})
.then(data => {
console.log('使用者資料:', data);
})
.catch(err => {
// 網路錯誤或上面的 throw 會跑到這裡
console.error('取得資料失敗:', err);
});
重點:
response.ok為true時代表狀態碼在 200~299 之間,建議在解析前先檢查。
範例 2:POST JSON 資料
const payload = {
title: 'Fetch 教學',
body: '這是一篇使用 fetch() 的教學文章。',
userId: 1
};
fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST', // 使用 POST 方法
headers: {
'Content-Type': 'application/json' // 告訴伺服器傳送的是 JSON
},
body: JSON.stringify(payload) // 把物件轉成字串
})
.then(res => res.json())
.then(result => {
console.log('建立成功的回傳資料:', result);
})
.catch(console.error);
範例 3:使用 async/await 重寫 GET
async function getUser(username) {
try {
const response = await fetch(`https://api.github.com/users/${username}`);
if (!response.ok) {
// 直接拋出錯誤,讓 catch 捕捉
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json(); // 解析 JSON
return data; // 回傳結果給呼叫端
} catch (error) {
console.error('取得使用者失敗:', error);
// 依需求可回傳 null 或重新拋出錯誤
return null;
}
}
// 呼叫方式
getUser('octocat').then(user => console.log(user));
範例 4:同時發送多筆請求(Promise.all)
const urls = [
'https://jsonplaceholder.typicode.com/posts/1',
'https://jsonplaceholder.typicode.com/posts/2',
'https://jsonplaceholder.typicode.com/posts/3'
];
// 把每個 URL 包成 fetch Promise
const promises = urls.map(url => fetch(url).then(r => r.json()));
Promise.all(promises)
.then(results => {
console.log('三筆資料一次取得:', results);
})
.catch(err => {
console.error('其中一筆請求失敗:', err);
});
小技巧:若想保證所有請求都必須成功,使用
Promise.all;若想即使部分失敗也能取得其他結果,可改用Promise.allSettled。
範例 5:自訂逾時(Timeout)機制
fetch() 本身不支援逾時,需要自行包裝:
function fetchWithTimeout(url, options = {}, timeout = 5000) {
// 建立一個會在 timeout 後 reject 的 Promise
const timeoutPromise = new Promise((_, reject) => {
const id = setTimeout(() => {
clearTimeout(id);
reject(new Error('Request timed out'));
}, timeout);
});
// 用 Promise.race 同時執行 fetch 與 timeout
return Promise.race([fetch(url, options), timeoutPromise]);
}
// 使用方式
fetchWithTimeout('https://jsonplaceholder.typicode.com/posts/1', {}, 3000)
.then(res => res.json())
.then(data => console.log('取得資料:', data))
.catch(err => console.error('錯誤或逾時:', err));
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方案 |
|---|---|---|
忘記檢查 response.ok |
直接呼叫 response.json(),即使是 404/500 仍會解析,導致程式誤以為成功。 |
在解析前先判斷 response.ok,必要時拋出錯誤。 |
body 不是字串 |
fetch 的 body 必須是字串或 FormData,直接傳物件會拋 TypeError。 |
使用 JSON.stringify() 或 URLSearchParams 轉換。 |
| 跨域 (CORS) 錯誤 | 瀏覽器會阻擋未授權的跨域請求,錯誤訊息不容易看出。 | 確認伺服器端已設定 Access-Control-Allow-Origin,或使用 JSONP / 代理伺服器。 |
忘記 await 或 then |
忽略 Promise,導致程式在未取得資料前就往下執行。 | 使用 async/await 或正確鏈接 then。 |
| 逾時未處理 | fetch 在網路卡住時會一直等下去,使用者體驗差。 |
自行實作逾時(如上例),或使用第三方函式庫(axios、ky)。 |
最佳實踐:
- 統一錯誤處理:封裝
fetch為request()函式,內部集中處理response.ok、JSON 解析與例外拋出。 - 使用
async/await:讓非同步流程更直觀,尤其在多層then時。 - 設定合理的
Cache-Control、ETag:減少不必要的請求,提升效能。 - 在開發環境使用 Mock Server:避免頻繁呼叫真實 API,提升測試效率。
- 避免在 URL 中直接拼接使用者輸入:使用
encodeURIComponent防止注入攻擊。
實際應用場景
| 場景 | 需求 | fetch 實作要點 |
|---|---|---|
| 取得部落格文章列表 | 前端頁面首次載入時向 CMS API 拉資料。 | 使用 GET,搭配 cache: 'no-store' 防止舊資料。 |
| 表單送出 | 使用者填寫表單後送出 JSON 到後端。 | POST + Content-Type: application/json,使用 await 等待回應後顯示成功訊息。 |
| 上傳檔案 | 圖片或影片上傳至雲端儲存服務。 | FormData 作為 body,fetch(url, { method: 'POST', body: form }),同時監聽 onprogress(需要 XHR)或使用分段上傳。 |
| 即時搜尋建議 | 輸入框每次變更即向搜尋 API 取得建議。 | 加入 防抖(debounce)機制,並設定 逾時 防止舊請求回覆覆寫新結果。 |
| 多語系資源載入 | 根據使用者語系動態載入 JSON 翻譯檔。 | 依語系建立 URL,使用 Promise.all 同時載入多個檔案,失敗時回退至預設語系。 |
總結
fetch() 為現代前端提供了 簡潔、可讀且基於 Promise 的網路請求方式。掌握以下幾點,即可在日常開發中得心應手:
- 先檢查
response.ok,確保 HTTP 狀態正確。 - 根據需求選擇正確的
Response方法(.json()、.text()、.blob()等)。 - 使用
async/await讓非同步流程更自然。 - 自行實作逾時、錯誤統一處理,提升使用者體驗與除錯效率。
- 注意 CORS、
body格式與跨域安全,避免常見的陷阱。
只要熟練上述概念與實作範例,你就能在 React、Vue、Angular 或純 Vanilla JS 專案中,輕鬆完成資料取得、送出與錯誤處理,為前端與後端之間的溝通搭建穩固的橋樑。祝你寫程式快樂,玩轉 fetch()!