Axios 請求取消:從原理到實踐
在現代前端開發中,網絡請求是不可或缺的一部分。Axios 是一個基于 Promise 的 HTTP 客戶端,廣泛應用于瀏覽器和 Node.js 環境中。然而,在某些場景下,我們可能需要取消正在進行的請求,例如用戶在請求完成前跳轉到其他頁面,或者重復觸發相同的請求時取消之前的請求。本文將深入探討 Axios 請求取消的原理,并通過一個二次封裝的例子來演示如何實現請求取消。
1. 請求取消的原理
Axios 的請求取消功能依賴于 CancelToken
。CancelToken
是一個用于取消請求的令牌,它可以通過 CancelToken.source()
方法創建。每個 CancelToken
實例都有一個 token
和一個 cancel
方法。當調用 cancel
方法時,與該 token
關聯的請求將被取消。
1.1 CancelToken 的工作原理
- 創建 CancelToken: 通過
CancelToken.source()
方法創建一個CancelToken
實例,該實例包含一個token
和一個cancel
方法。 - 關聯請求: 在發起請求時,將
token
傳遞給 Axios 請求配置中的cancelToken
字段。 - 取消請求: 當需要取消請求時,調用
cancel
方法,Axios 會中斷與該token
關聯的請求。
1.2 取消請求的流程
- 用戶觸發某個操作,發起一個請求。
- 在請求完成之前,用戶觸發了另一個操作,需要取消之前的請求。
- 調用
cancel
方法,Axios 中斷之前的請求。 - 發起新的請求。
2. 二次封裝 Axios 實現請求取消
為了更好地管理請求取消邏輯,我們可以對 Axios 進行二次封裝。以下是一個簡單的封裝示例,展示了如何在封裝中實現請求取消功能。
1. 代碼結構分析
1.1 Axios 實例的創建
const instance: AxiosInstance = axios.create({baseURL: import.meta.env.VITE_API_BASE_URL,timeout: 10000,headers: {'Content-Type': 'application/json',},
});
baseURL
: 從環境變量中獲取 API 的基礎 URL。timeout
: 設置請求超時時間為 10 秒。headers
: 設置默認請求頭為application/json
。
通過 axios.create
創建了一個 Axios 實例 instance
,后續的所有請求都將基于這個實例。
1.2 取消令牌的管理
const cancelTokenMap = new Map<string, CancelTokenSource>();
cancelTokenMap
: 使用Map
數據結構來存儲每個請求的取消令牌。鍵是請求的 URL,值是對應的CancelTokenSource
。- 作用: 通過 URL 快速查找和取消對應的請求。
1.3 請求攔截器
instance.interceptors.request.use((config) => {// 添加 token 到請求頭const token = localStorage.getItem('token');if (token) {config.headers = config.headers || {};config.headers.Authorization = `Bearer ${token}`;}return config;},(error) => {return Promise.reject(error);}
);
- 功能: 在請求發送前,檢查本地存儲中是否存在
token
,如果存在則將其添加到請求頭中。 - 作用: 實現全局的請求頭管理,例如身份驗證。
1.4 響應攔截器
instance.interceptors.response.use((response: AxiosResponse) => {// 清理已完成的請求記錄const url = response.config.url;if (url && cancelTokenMap.has(url)) {cancelTokenMap.delete(url);}// 處理全局響應邏輯if (response.data.code !== 0) {return Promise.reject(response.data);}return response.data;},(error) => {// 清理失敗的請求記錄const url = error.config?.url;if (url && cancelTokenMap.has(url)) {cancelTokenMap.delete(url);}// 處理全局錯誤if (error.response?.status === 401) {// 處理未授權window.location.href = '/login';}return Promise.reject(error);}
);
- 功能:
- 在請求成功時,清理
cancelTokenMap
中對應的請求記錄。 - 在請求失敗時,清理
cancelTokenMap
中對應的請求記錄,并根據狀態碼處理全局錯誤(例如未授權時跳轉到登錄頁)。
- 在請求成功時,清理
- 作用: 實現全局的響應和錯誤處理邏輯。
1.5 封裝的請求方法
const http = {get: <T>(url: string, config?: AxiosRequestConfig): Promise<T> => {const source = axios.CancelToken.source();cancelTokenMap.set(url, source);return instance.get(url, { ...config, cancelToken: source.token });},post: <T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> => {const source = axios.CancelToken.source();cancelTokenMap.set(url, source);return instance.post(url, data, { ...config, cancelToken: source.token });},put: <T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> => {const source = axios.CancelToken.source();cancelTokenMap.set(url, source);return instance.put(url, data, { ...config, cancelToken: source.token });},delete: <T>(url: string, config?: AxiosRequestConfig): Promise<T> => {const source = axios.CancelToken.source();cancelTokenMap.set(url, source);return instance.delete(url, { ...config, cancelToken: source.token });},// 取消指定請求cancelRequest: (url: string) => {const source = cancelTokenMap.get(url);if (source) {source.cancel(`Request canceled: ${url}`);cancelTokenMap.delete(url);}},// 取消所有請求cancelAllRequests: () => {cancelTokenMap.forEach((source, url) => {source.cancel(`Request canceled: ${url}`);cancelTokenMap.delete(url);});},
};
- 功能:
- 封裝了
get
、post
、put
、delete
方法,每個方法都會為請求創建一個CancelToken
,并將其存儲到cancelTokenMap
中。 - 提供了
cancelRequest
和cancelAllRequests
方法,用于取消指定請求或所有請求。
- 封裝了
- 作用: 簡化請求調用,并提供靈活的請求取消功能。
2. 請求取消的實現原理
2.1 CancelToken 的作用
CancelToken.source()
: 創建一個CancelTokenSource
對象,包含token
和cancel
方法。token
: 用于關聯請求。cancel
: 用于取消請求。
2.2 請求取消的流程
- 發起請求時,創建一個
CancelTokenSource
,并將其存儲到cancelTokenMap
中。 - 如果需要取消請求,調用
cancelRequest
或cancelAllRequests
方法。 - 調用
cancel
方法后,Axios 會中斷與該token
關聯的請求,并拋出一個Cancel
錯誤。 - 在響應攔截器中,清理已完成的請求記錄。
3. 使用場景
3.1 取消重復請求
當用戶快速點擊按鈕多次觸發相同的請求時,可以通過 cancelRequest
方法取消之前的請求,只保留最后一次請求。
http.get('/api/data').then((data) => console.log(data)).catch((error) => {if (axios.isCancel(error)) {console.log('Request canceled:', error.message);} else {console.error('Error:', error);}});// 取消之前的請求
http.cancelRequest('/api/data');
3.2 頁面跳轉時取消請求
當用戶跳轉到其他頁面時,可以通過 cancelAllRequests
方法取消所有未完成的請求,避免無效請求占用資源。
window.addEventListener('beforeunload', () => {http.cancelAllRequests();
});
完整代碼
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, CancelTokenSource } from 'axios';// 創建axios實例
const instance: AxiosInstance = axios.create({baseURL: import.meta.env.VITE_API_BASE_URL,timeout: 10000,headers: {'Content-Type': 'application/json',},
});// 創建一個Map來存儲取消令牌
const cancelTokenMap = new Map<string, CancelTokenSource>();// 請求攔截器
instance.interceptors.request.use((config) => {// 在這里可以添加token等全局請求頭const token = localStorage.getItem('token');if (token) {config.headers = config.headers || {};config.headers.Authorization = `Bearer ${token}`;}return config;},(error) => {return Promise.reject(error);}
);// 響應攔截器
instance.interceptors.response.use((response: AxiosResponse) => {// 清理已完成的請求記錄const url = response.config.url;if (url && cancelTokenMap.has(url)) {cancelTokenMap.delete(url);}// 在這里處理全局響應邏輯if (response.data.code !== 0) {return Promise.reject(response.data);}return response.data;},(error) => {// 清理失敗的請求記錄const url = error.config?.url;if (url && cancelTokenMap.has(url)) {cancelTokenMap.delete(url);}// 在這里處理全局錯誤if (error.response?.status === 401) {// 處理未授權window.location.href = '/login';}return Promise.reject(error);}
);// 封裝請求方法
const http = {get: <T>(url: string, config?: AxiosRequestConfig): Promise<T> => {const source = axios.CancelToken.source();cancelTokenMap.set(url, source);return instance.get(url, { ...config, cancelToken: source.token });},post: <T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> => {const source = axios.CancelToken.source();cancelTokenMap.set(url, source);return instance.post(url, data, { ...config, cancelToken: source.token });},put: <T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> => {const source = axios.CancelToken.source();cancelTokenMap.set(url, source);return instance.put(url, data, { ...config, cancelToken: source.token });},delete: <T>(url: string, config?: AxiosRequestConfig): Promise<T> => {const source = axios.CancelToken.source();cancelTokenMap.set(url, source);return instance.delete(url, { ...config, cancelToken: source.token });},// 添加取消請求的方法// http.cancelRequest('/api/some-endpoint');cancelRequest: (url: string) => {const source = cancelTokenMap.get(url);if (source) {source.cancel(`Request canceled: ${url}`);cancelTokenMap.delete(url);}},// 取消所有請求cancelAllRequests: () => {cancelTokenMap.forEach((source, url) => {source.cancel(`Request canceled: ${url}`);cancelTokenMap.delete(url);});},
};export default http;
4. 總結
通過二次封裝 Axios,我們實現了一個功能強大且易于使用的 HTTP 客戶端。它不僅支持全局的請求和響應攔截,還提供了靈活的請求取消功能,適用于多種場景。希望本文能幫助你更好地理解和使用 Axios 的請求取消功能。