在進行網絡請求時,JavaScript 提供了多種方式來與伺服器進行通信。最常見的三種方式是 XMLHttpRequest
、Fetch
和 Axios
。在探討它們的差異、優缺點以及使用場景之前,可以先閱讀上一篇文章 JavaScript AJAX
XMLHttpRequest
XMLHttpRequest
(XHR)是最早期用於在瀏覽器與伺服器之間傳輸資料的 API,它允許在不重新加載整個頁面的情況下進行 HTTP 請求。
語法
var xhr = new XMLHttpRequest()
,代表產生一個 XHR 物件xhr.open(方法, URL, [是否為非同步])
xhr.send()
送出請求xhr.addEventListener("load", callback)
使用 Callback 等資料回來- XHR 有各種狀態碼 (readyState),不是 HTTP Status Code 哦!
xhr.readyState = 0
,代表已經產生 XMLHttpRequest
,但還沒發xhr.readyState = 1
,代表用了 open()
,但還沒傳送資料過去xhr.readyState = 2
,代表用了 send()
,已讀取xhr.readyState = 3
,代表還在載入資料xhr.readyState = 4
,代表載入完畢,資料已經完全
xhr.status
代表 HTTP Status Code
範例
範例1 - 取得資料
1
2
3
4
5
6
| var xhr = new XMLHttpRequest();
xhr.open("GET", "./data.json");
xhr.addEventListener("load", function() {
console.log(xhr.responseText);
});
xhr.send();
|
1
2
3
4
5
6
7
8
9
10
| var xhr = new XMLHttpRequest();
xhr.open("POST", "./login");
xhr.setRequestHeader(
"Content-type",
"application/x-www-form-urlencoded"
);
xhr.addEventListener("load", function() {
console.log(xhr.responseText);
});
xhr.send("name=elaine&password=123");
|
範例3 - 登入 (application/json)
1
2
3
4
5
6
7
8
9
| var account = {
name: "elaine",
password: "123",
};
var xhr = new XMLHttpRequest();
xhr.open("post", "https://xxx", true);
xhr.setRequestHeader("Content-type", "application/json");
var data = JSON.stringify(account);
xhr.send(data);
|
完整範例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
| <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>練習 XMLHttpRequest</title>
</head>
<body>
<textarea id="responseText"></textarea>
<script>
function success(text) {
var textarea = document.getElementById("responseText");
textarea.value = text;
}
function fail(code) {
var textarea = document.getElementById("responseText");
textarea.value = "Error code: " + code;
}
function pending() {
var textarea = document.getElementById("responseText");
textarea.value = "loading...";
}
var request = new XMLHttpRequest();
// 狀態發生變化時會執行這個 callback
request.onreadystatechange = function() {
// 判斷是否請求完成
if (request.readyState === 4) {
// 判斷 status code 是否為成功
if (request.status === 200) {
return success(request.responseText);
} else {
return fail(request.status);
}
} else {
return pending();
}
};
// 設定 URL 與 HTTP 方法
request.open("GET", "/users");
// 發送請求
request.send();
</script>
</body>
</html>
|
優點
- 瀏覽器支援度廣泛:XHR 在所有現代瀏覽器中都被支援
缺點
- 複雜性高:語法相對複雜,需要處理多種狀態和事件
- 可讀性差:相比於
Fetch
和 Axios
,XHR
的程式碼可讀性較差
Fetch
Fetch
是一種現代化的接口,用於發送 HTTP 請求。它基於 Promise
,提供了更簡潔的語法和更好的可讀性
語法
fetch(url, options)
- url:請求的 URL
options,常見的欄位如下
1
2
3
4
5
6
7
8
| fetch(url, {
body: JSON.stringify(data), // request body 要用 json 格式,記得要搭配 header 的 content-type
credentials: "same-origin", // 決定 cookie 要不要帶過去 server,可以設定 include, same-origin, omit
headers: {
"content-type": "application/json", // 跟 server 說我要傳 JSON 格式過去囉
},
method: "POST", // http method 可以帶 GET, POST, PUT, PATCH, DELETE,預設是 GET
})
|
- response 物件
response.status
:一個整數(預設值為 200),包含回應的狀態碼response.statusText
:一個字串(預設值為 “OK”),對應於 HTTP 狀態碼訊息response.ok
:這是一個檢查 HTTP status code 是否在 200-299 範圍內的簡寫。這會回傳一個 bool
- server 回傳的資料是 JSON 格式的話,要使用
await response.json()
來解析出來 object
注意:
fetch()
回傳的 promise
不會 reject HTTP 的 error status,就算是 HTTP 404 或 500 也一樣。相反地,它會正常地 resolve,並把 ok
status 設為 false
。會讓它發生 reject 的只有網路錯誤或其他會中斷 request 的情況- 記得
response.json()
是一個 Promise
,要用 .then()
或是 await
才可以解出來
範例
範例1:取得資料 (使用 GET & 解析 response JSON 資料)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| async function fetchData() {
try {
const response = await fetch('./data.json');
if (!response.ok) {
throw new Error(`Network response was not ok: ${response.statusText}`);
}
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Fetch error:', error);
}
}
fetchData();
|
範例2:登入 (使用 POST & request body 帶 JSON)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| async function login() {
const account = {
name: "elaine",
password: "123",
};
try {
const response = await fetch('https://xxx', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(account)
});
if (!response.ok) {
throw new Error(`Network response was not ok: ${response.statusText}`);
}
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Fetch error:', error);
}
}
login();
|
優點
- 簡潔語法:基於
Promise
,使程式碼更簡潔和易於理解 - 更好的錯誤處理:可以通過
.then()
和 .catch()
方法處理請求和錯誤,也可以用 async await
搭配 try..catch
缺點
- 支援限制:不支援所有舊版瀏覽器(如 IE)
- 錯誤處理不足:
Fetch
只會在網絡錯誤時拒絕 Promise
,對於 4xx 或 5xx 狀態碼需要手動檢查 - 不支援進度事件:
- XMLHttpRequest 支援進度事件,可以追蹤請求的上傳和下載進度
- fetch API 則不支援進度事件,這在需要進度監控的場景中是一個限制
Axios
Axios 是一個基於 Promise
的 HTTP 客戶端,適用於瀏覽器和 Node.js。它提供了更豐富的功能和更簡潔的 API,但是他不是原生的,需要安裝
安裝 Axios
可以使用 npm install --save axios
,也可以使用 CDN
方式載入
1
| <script src="https://cdn.jsdelivr.net/npm/axios@1.6.7/dist/axios.min.js"></script>
|
詳細的安裝方式請看 Github - Axios Installing
語法
1
2
3
4
5
6
7
8
9
10
| import axios from 'axios';
async function getUser() {
try {
const response = await axios.get('/user?ID=12345');
console.log(response);
} catch (error) {
console.error(error);
}
}
|
設定 Base URL
你可以通過設定 Axios
的實例來設置一個全局的 base URL,這樣所有的請求都會自動使用這個 base URL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| const apiClient = axios.create({
baseURL: 'https://api.example.com'
});
async function fetchData() {
try {
const response = await apiClient.get('/endpoint');
console.log(response.data);
} catch (error) {
console.error('API request error:', error);
}
}
fetchData();
|
設定 timeout
1
2
3
4
| const apiClient = axios.create({
baseURL: 'https://api.example.com'
timeout: 3000, // 3000 代表 3s,default 是 0 (代表沒有 timeout)
});
|
攔截器
攔截器允許你在請求或回應被處理之前進行操作。以下是設定請求和回應攔截器的範例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
| const apiClient = axios.create({
baseURL: 'https://api.example.com'
});
// request 攔截器
apiClient.interceptors.request.use((config) => {
console.log('Request sent:', config);
config.headers.Authorization = `Bearer ${yourToken}`; // 常見的是 request header 加上 token
return config;
}, error => {
// 在請求發送之前,如果出現錯誤(例如,請求配置不正確)
return Promise.reject(error);
});
// response 攔截器
apiClient.interceptors.response.use((response) => {
console.log('Response received:', response);
return response;
}, (error) => {
// response error 攔截器
console.error('Response error:', error);
return Promise.reject(error);
});
async function fetchDataWithInterceptors() {
try {
const response = await apiClient.get('/endpoint');
console.log(response.data);
} catch (error) {
console.error('API request error:', error);
}
}
fetchDataWithInterceptors();
|
Cancel Request
從 v0.22.0 開始,Axios
支援使用原生的 AbortController
來以取消請求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| const controller = new AbortController();
async function fetchData() {
try {
const response = await axios.get('/user/12345', {
signal: controller.signal
});
console.log(response.data);
} catch (error) {
if (axios.isCancel(error)) {
// 處理請求取消的錯誤
console.log('請求已取消', error.message);
} else {
// 處理錯誤
console.error(error);
}
}
}
fetchData();
// 取消 Request
controller.abort()
|
在 Axios 0.22.0 之前,取消請求使用的是 CancelToken,這是 Axios 自定義的一種取消機制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| const cancelTokenSource = axios.CancelToken.source();
async function fetchData() {
try {
const response = await axios.get('/user/12345', {
cancelToken: cancelTokenSource.token
});
console.log(response.data);
} catch (error) {
if (axios.isCancel(error)) {
console.log('請求已取消', error.message);
} else {
console.error(error);
}
}
}
fetchData();
// 取消 Request(訊息參數是 optional 的)
cancelTokenSource.cancel('操作已被使用者取消');
|
完整取消 Request 範例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
| <!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<title>Axios 請求取消範例</title>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
</head>
<body>
<button id="fetchDataBtn">獲取數據</button>
<button id="cancelRequestBtn">取消請求</button>
<div id="result"></div>
<script>
document.getElementById('fetchDataBtn').addEventListener('click', fetchData);
document.getElementById('cancelRequestBtn').addEventListener('click', cancelRequest);
let controller;
async function fetchData() {
// 創建一個新的 AbortController
controller = new AbortController();
try {
const response = await axios.get('https://jsonplaceholder.typicode.com/posts', {
signal: controller.signal
});
document.getElementById('result').textContent = JSON.stringify(response.data, null, 2);
} catch (error) {
if (axios.isCancel(error)) {
document.getElementById('result').textContent = '請求已取消';
} else {
document.getElementById('result').textContent = '錯誤: ' + error.message;
}
}
}
function cancelRequest() {
if (controller) {
// 取消請求
controller.abort();
document.getElementById('result').textContent = '正在取消請求...';
}
}
</script>
</body>
</html>
|
範例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| async function login() {
try {
const params = new URLSearchParams({
name: 'elaine',
password: '123'
});
const response = await axios.post('/login', params);
console.log(response.data);
} catch (error) {
console.error(error);
}
}
login();
|
範例2 - 登入 (application/json)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| async function login() {
try {
const account = {
name: 'elaine',
password: '123'
};
const response = await axios.post('/login', account);
console.log(response.data);
} catch (error) {
console.error(error);
}
}
login();
|
優點
- 簡潔語法:更簡潔的語法和更好的可讀性
- 自動轉換:自動轉換
JSON
資料 - 攔截器:提供請求和回應攔截器,方便處理請求和回應
- 更好的錯誤處理:對 4xx 和 5xx 錯誤有更好的處理
- 更靈活的配置:例如取消請求、設置超時、配置全局的 base URL,這些配置使得 Axios 更加靈活且易於使用
缺點
- 額外依賴:
Axios
不是原生的,需要額外引入 Axios
套件
總結
XMLHttpRequest
:適合需要對請求和回應進行詳細控制的場景,但語法相對複雜,可讀性差Fetch
:適合現代瀏覽器和簡單的網絡請求,語法簡潔,但對錯誤處理不夠完善Axios
:提供了更豐富的功能和更簡潔的 API,適合需要處理多種請求和回應邏輯的場景,但不是原生的,所以需要額外引入套件
我是推薦使用 Axios
啦!舒服又好用!
參考資料