JavaScript Fetch API
Fetch API 是現代瀏覽器內建的 API,用來發送 HTTP 請求,是 XMLHttpRequest (AJAX) 的現代替代方案。Fetch 使用 Promise,語法更簡潔,也更容易和 async/await 搭配使用。
基本用法
fetch() 函式接收一個 URL 作為參數,回傳一個 Promise:
fetch('https://api.example.com/data')
.then(function(response) {
return response.json();
})
.then(function(data) {
console.log(data);
})
.catch(function(err) {
console.error('請求失敗:' + err);
});
使用 async/await 的寫法更簡潔:
async function getData() {
try {
var response = await fetch('https://api.example.com/data');
var data = await response.json();
console.log(data);
} catch (err) {
console.error('請求失敗:' + err);
}
}
Response 物件
fetch() 回傳的 Promise 會 resolve 成一個 Response 物件,包含以下常用屬性和方法:
屬性
var 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) {
var response = await fetch(url);
if (!response.ok) {
throw new Error('HTTP 錯誤:' + response.status);
}
return response.json();
}
// 使用
try {
var data = await fetchData('/api/user');
} catch (err) {
console.error(err.message);
}
發送 POST 請求
fetch() 的第二個參數是一個選項物件,用來設定請求方法、標頭和內容:
async function createUser(userData) {
var 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();
}
// 使用
var 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 物件
var headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('Authorization', 'Bearer token123');
await fetch('/api/data', { headers: headers });
發送表單資料
發送 FormData
var formData = new FormData();
formData.append('username', 'mike');
formData.append('password', '123456');
await fetch('/api/login', {
method: 'POST',
body: formData // 不需要設定 Content-Type,瀏覽器會自動設定
});
發送 URL 編碼的表單
var 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
});
上傳檔案
var fileInput = document.querySelector('input[type="file"]');
var 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) {
var controller = new AbortController();
var timeoutId = setTimeout(function() {
controller.abort();
}, timeout);
try {
var response = await fetch(url, {
signal: controller.signal
});
clearTimeout(timeoutId);
return response;
} catch (err) {
if (err.name === 'AbortError') {
throw new Error('請求逾時');
}
throw err;
}
}
// 5 秒逾時
var response = await fetchWithTimeout('/api/data', 5000);
取消請求
使用 AbortController 可以取消進行中的請求:
var 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 {
var response = await fetch('https://api.other-domain.com/data', {
mode: 'cors', // 預設值
credentials: 'include' // 如果需要帶入 cookies
});
var data = await response.json();
} catch (err) {
console.error('跨域請求失敗:' + err);
}
mode 選項的可能值:
cors:允許跨域請求(預設)same-origin:只允許同源請求no-cors:允許跨域請求,但無法讀取回應內容
實用範例
封裝 API 請求函式
async function api(endpoint, options) {
var baseUrl = 'https://api.example.com';
var defaultOptions = {
headers: {
'Content-Type': 'application/json'
}
};
var response = await fetch(baseUrl + endpoint,
Object.assign({}, defaultOptions, options)
);
if (!response.ok) {
var error = await response.json();
throw new Error(error.message || 'API 錯誤');
}
return response.json();
}
// 使用
var users = await api('/users');
var newUser = await api('/users', {
method: 'POST',
body: JSON.stringify({ name: 'Mike' })
});
Fetch vs XMLHttpRequest
| 功能 | Fetch | XMLHttpRequest |
|---|---|---|
| 語法 | Promise-based,簡潔 | 回呼函式,較冗長 |
| 串流處理 | 支援 | 有限支援 |
| 取消請求 | AbortController | xhr.abort() |
| 進度事件 | 不直接支援 | 支援 |
| 瀏覽器支援 | 現代瀏覽器 | 所有瀏覽器 |
如果你的專案不需要支援舊版瀏覽器(如 IE),建議使用 Fetch API。