在進行網絡請求時,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,並把- okstatus 設為- 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 啦!舒服又好用!
參考資料