JavaScript Fetch API
Fetch API 是現代瀏覽器內建的 API,用來發送 HTTP 請求,是 XMLHttpRequest (AJAX) 的現代替代方案。Fetch 使用 Promise,語法更簡潔,也更容易和 async/await 搭配使用。
基本用法
fetch() 函式接收一個 URL 作為參數,回傳一個 Promise:
// 取得遠端資料
fetch('https://jsonplaceholder.typicode.com/posts/1')
.then((response) => response.json())
.then((json) => console.log(json));
// 使用 async/await 語法 (推薦)
async function getData() {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/posts/1');
const json = await response.json();
console.log(json);
} catch (error) {
console.error(error);
}
}
getData();
Response 物件
fetch() 回傳的 Promise 會 resolve 成一個 Response 物件,包含以下常用屬性和方法:
屬性
const response = await fetch('/api/data');
response.ok; // 狀態碼在 200-299 之間為 true
response.status; // HTTP 狀態碼,如 200, 404, 500
response.statusText; // 狀態文字,如 "OK", "Not Found"
response.headers; // Headers 物件
response.url; // 請求的 URL
讀取回應內容
Response 提供多種方法來讀取回應內容,這些方法都回傳 Promise:
response.json(); // 解析為 JSON
response.text(); // 解析為純文字
response.blob(); // 解析為 Blob(二進位資料)
response.arrayBuffer(); // 解析為 ArrayBuffer
response.formData(); // 解析為 FormData
這些方法只能呼叫一次,因為 response body 只能被讀取一次。
檢查請求是否成功
fetch 只有在網路錯誤時才會 reject,HTTP 錯誤狀態碼(如 404、500)不會 reject。所以需要手動檢查 response.ok:
async function fetchData(url) {
const response = await fetch(url);
if (!response.ok) {
throw new Error('HTTP 錯誤:' + response.status);
}
return response.json();
}
// 使用
try {
const data = await fetchData('/api/user');
} catch (err) {
console.error(err.message);
}
發送 POST 請求
fetch() 的第二個參數是一個選項物件,用來設定請求方法、標頭和內容:
async function createUser(userData) {
const response = await fetch('/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(userData),
});
if (!response.ok) {
throw new Error('建立失敗');
}
return response.json();
}
// 使用
const newUser = await createUser({
name: 'Mike',
email: 'mike@example.com',
});
其他 HTTP 方法
// PUT 請求
await fetch('/api/users/1', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ name: 'Mike Lee' }),
});
// PATCH 請求
await fetch('/api/users/1', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ name: 'Mike Lee' }),
});
// DELETE 請求
await fetch('/api/users/1', {
method: 'DELETE',
});
設定 Headers
// 方法一:在選項物件中設定
await fetch('/api/data', {
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer token123',
},
});
// 方法二:使用 Headers 物件
const headers = new Headers({
'Content-Type': 'text/plain',
'X-Custom-Header': 'ProcessThisImmediately',
});
fetch('/api/header', {
headers: headers,
});
發送表單資料
發送 FormData
const formData = new FormData();
formData.append('username', 'mike');
formData.append('password', '123456');
await fetch('/api/login', {
method: 'POST',
body: formData, // 不需要設定 Content-Type,瀏覽器會自動設定
});
發送 URL 編碼的表單
const params = new URLSearchParams();
params.append('username', 'mike');
params.append('password', '123456');
await fetch('/api/login', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: params,
});
上傳檔案
const fileInput = document.querySelector('input[type="file"]');
const formData = new FormData();
formData.append('file', fileInput.files[0]);
await fetch('/api/upload', {
method: 'POST',
body: formData,
});
設定請求逾時
fetch 原生不支援 timeout,但可以用 AbortController 實作:
async function fetchWithTimeout(url, timeout) {
const controller = new AbortController();
const timeoutId = setTimeout(function () {
controller.abort();
}, timeout);
try {
const response = await fetch(url, {
signal: controller.signal,
});
clearTimeout(timeoutId);
return response;
} catch (err) {
if (err.name === 'AbortError') {
throw new Error('請求逾時');
}
throw err;
}
}
// 5 秒逾時
const response = await fetchWithTimeout('/api/data', 5000);
取消請求
使用 AbortController 可以取消進行中的請求:
const controller = new AbortController();
// 發送請求
fetch('/api/data', {
signal: controller.signal,
})
.then(function (response) {
return response.json();
})
.catch(function (err) {
if (err.name === 'AbortError') {
console.log('請求被取消');
}
});
// 取消請求
controller.abort();
帶入 Cookies
預設情況下,fetch 在同源請求會帶入 cookies,跨域請求則不會。使用 credentials 選項可以控制這個行為:
// 同源請求帶入 cookies(預設)
await fetch('/api/data', {
credentials: 'same-origin',
});
// 跨域請求也帶入 cookies
await fetch('https://other-domain.com/api', {
credentials: 'include',
});
// 不帶入 cookies
await fetch('/api/data', {
credentials: 'omit',
});
CORS 跨域請求
當你向不同網域發送請求時,會受到瀏覽器的同源政策 (Same-Origin Policy) 限制。伺服器需要設定適當的 CORS 標頭才能允許跨域請求。
// 跨域請求
try {
const response = await fetch('https://api.other-domain.com/data', {
mode: 'cors', // 預設值
credentials: 'include', // 如果需要帶入 cookies
});
const data = await response.json();
} catch (err) {
console.error('跨域請求失敗:' + err);
}
mode 選項的可能值:
cors:允許跨域請求(預設)same-origin:只允許同源請求no-cors:允許跨域請求,但無法讀取回應內容
實用範例
封裝 API 請求函式
async function api(endpoint, options) {
const baseUrl = 'https://api.example.com';
const defaultOptions = {
headers: {
'Content-Type': 'application/json',
},
};
const data = { username: 'example' };
fetch('https://example.com/profile', {
method: 'POST', // or 'PUT'
body: JSON.stringify(data), // data can be `string` or {object}!
headers: new Headers({
'Content-Type': 'application/json',
}),
})
.then((res) => res.json())
.catch((error) => console.error('Error:', error))
.then((response) => console.log('Success:', response));
const response = await fetch(baseUrl + endpoint, Object.assign({}, defaultOptions, options));
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || 'API 錯誤');
}
return response.json();
}
// 使用
const users = await api('/users');
const newUser = await api('/users', {
method: 'POST',
body: JSON.stringify({ name: 'Mike' }),
});
Fetch vs XMLHttpRequest
| 功能 | Fetch | XMLHttpRequest |
|---|---|---|
| 語法 | Promise-based,簡潔 | 回呼函式,較冗長 |
| 串流處理 | 支援 | 有限支援 |
| 取消請求 | AbortController | xhr.abort() |
| 進度事件 | 不直接支援 | 支援 |
| 瀏覽器支援 | 現代瀏覽器 | 所有瀏覽器 |
如果你的專案不需要支援舊版瀏覽器(如 IE),建議使用 Fetch API。