處理網絡請求時,我們經常會遇到需要中途取消請求的情況,比如用戶在兩個tab之間反復橫跳的場景,如果每個接口都從頭請求到結束,那必然會造成很大的服務壓力。
AbortController是一個Web API,它提供了一個信號對象(AbortSignal),該對象可以用來取消與Fetch API相關的操作。當我們創建AbortController實例時,會自動生成一個與之關聯的AbortSignal對象。我們可以將這個AbortSignal對象作為參數傳遞給fetch函數,從而實現對網絡請求的取消控制。
import axios from 'axios'
import QS from 'qs'//引入qs模塊,用來序列化post類型的數據// 創建map存儲未返回response的接口請求
const pendingRequests = new Map();const generateRequestKey = (config) => {// 處理請求數據,確保請求和響應時一致,response返回的config中可能存在序列化的data,需要轉換成json格式,否則生成的key不一致const normalizeData = (data) => {if (typeof data === 'string') {try {return JSON.parse(data);} catch {return data;}}return data;};return [config.method,config.url,JSON.stringify(normalizeData(config.params) || {}),JSON.stringify(normalizeData(config.data) || {})].join('|');};axios.defaults.baseURL = '/';
axios.defaults.timeout = 10000;
axios.defaults.headers.post['Content-Type'] = 'application/json';
// 如果需要跨域,可以設置withCredentials為true
axios.defaults.withCredentials = true; // 允許跨域請求時發送cookies// 創建axios實例
const service = axios.create({baseURL: '/api', // api的base_urltimeout: 10000,// 請求超時時間headers: {'Access-Control-Allow-Origin': '*','strict-origin-when-cross-origin': '*','Cache-Control': 'no-cache','Content-Type': 'application/x-www-form-urlencoded','userRole': 'WEB','Accept-Language': i18n.locale || localStorage.getItem('Accept-Language')}
});// 請求攔截器
service.interceptors.request.use(config => {config.headers['nh-token'] = localStorage.getItem('NH_TOKEN') || ""const token = localStorage.getItem('newToken') || ""if (token) config.headers['Authorization'] = 'Bearer ' + token // 新服務添加token// 生成請求key,用于取消重復的相同請求const requestKey = generateRequestKey(config, 'service.interceptors.request');// 如果存在相同請求,取消前一個if (pendingRequests.has(requestKey)) {const abortController = pendingRequests.get(requestKey);abortController.abort();}// 為當前請求創建新的控制器const controller = new AbortController();config.signal = controller.signal;pendingRequests.set(requestKey, controller);return config;
},error => {return Promise.error(error);})
// 響應攔截器
service.interceptors.response.use(response => {const requestKey = generateRequestKey(response.config, 'service.interceptors.response');pendingRequests.delete(requestKey);// console.log('Response=>', response.request.responseURL, response );if (response.status === 200) {//result 0-正常 1-異常 10000-未登錄 4000-系統異常if (response.data.result == 10000) {// 登錄失敗,跳轉到首頁,重新登錄router.push({ path: '/login' })return Promise.resolve(response.data)} else {return Promise.resolve(response.data)}} else {return Promise.reject(response);}},error => {if (error.name === 'AbortError') {console.log('請求已被取消:', error.message);}// 取消請求時,不執行這里的代碼const requestKey = generateRequestKey(error.config || {});pendingRequests.delete(requestKey);if (error.status) {return Promise.reject(error.response);}})
export default service
在接口封裝層按照如上進行,可以滿足接口重復請求時,取消重復的操作。需要注意一點,config.data,從request層獲取的是Object,但是從response層獲取的是一個JSON化的String。所以通過normalizeData方法,進行數據解析,防止map找不到。