如何取消無用的HTTP請求

在開發前端應用時,我們經常會遇到需要取消HTTP請求的情況。比如文件上傳過程中用戶想取消上傳,或者搜索框在用戶連續輸入時取消之前的搜索請求。取消無用的請求既能節省服務器資源,又能提升用戶體驗。
為什么要取消請求?
想象這樣一個場景:用戶上傳一個大文件,上傳到一半時發現選錯了文件。如果不取消當前的上傳請求,用戶要么等待上傳完成(浪費時間),要么關閉頁面(體驗不好)。最好的做法是立即取消當前請求,讓用戶重新選擇文件。
下面介紹幾種常用的取消請求方法。
1. 原生AJAX的abort方法
XMLHttpRequest提供了abort()方法來取消請求:
// 創建XMLHttpRequest對象
const xhr = new XMLHttpRequest();
const url = "https://api.example.com/data";
// 初始化請求
xhr.open('GET', url);
// 監聽取消事件
xhr.addEventListener('abort', function() {
console.log('請求已被取消');
});
// 發送請求
xhr.send();
// 在需要的時候取消請求
setTimeout(() => {
xhr.abort(); // 取消請求
console.log('取消后的狀態碼:', xhr.status); // 變為0
}, 100);當調用abort()后,請求會立即停止,status狀態碼會變成0。
2. Fetch API使用AbortController
Fetch API需要使用AbortController來取消請求:
// 創建AbortController實例
const controller = new AbortController();
const signal = controller.signal;
// 發起請求
fetch('https://api.example.com/submit', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ name: '張三', age: 25 }),
signal: signal // 關聯信號量
})
.then(response => {
if (!response.ok) thrownewError('請求失敗');
return response.json();
})
.then(data => {
console.log('提交成功:', data);
})
.catch(error => {
if (error.name === 'AbortError') {
console.log('請求已被取消');
} else {
console.error('請求錯誤:', error);
}
});
// 取消請求
setTimeout(() => {
controller.abort();
}, 1000);重要提示:一個AbortController只能使用一次。取消請求后,如果需要發起新的可取消請求,需要創建新的AbortController實例。
3. Axios使用AbortController(推薦)
Axios從0.22.0版本開始推薦使用AbortController:
// 創建控制器
const controller = new AbortController();
// 方法一:監聽取消事件
controller.signal.addEventListener("abort", () => {
console.log("請求已取消");
});
// 方法二:使用try-catch
asyncfunctionuploadFile() {
try {
const response = await axios.post('/api/upload', formData, {
signal: controller.signal
});
console.log('上傳成功', response.data);
} catch (error) {
if (error.message === "canceled") {
console.log("請求已被取消");
} else {
console.error("上傳錯誤:", error);
}
}
}
// 取消請求
controller.abort();4. Axios的CancelToken(舊版本)
0.22.0之前的Axios使用CancelToken,現在已不推薦使用:
import axios from"axios";
// 創建取消令牌
const source = axios.CancelToken.source();
try {
const response = await axios.post('/api/upload', formData, {
cancelToken: source.token
});
} catch (error) {
if (axios.isCancel(error)) {
console.log('請求已取消:', error.message);
}
}
// 取消請求
source.cancel('用戶取消了操作');實際應用場景
場景1:搜索框防抖
let controller = null;
asyncfunctionhandleSearch(keyword) {
// 取消之前的請求
if (controller) {
controller.abort();
}
// 創建新的控制器
controller = new AbortController();
try {
const response = await fetch(`/api/search?q=${keyword}`, {
signal: controller.signal
});
const results = await response.json();
displayResults(results);
} catch (error) {
if (error.name !== 'AbortError') {
console.error('搜索錯誤:', error);
}
}
}
// 防抖處理
const debouncedSearch = debounce(handleSearch, 300);
searchInput.addEventListener('input', (e) => {
debouncedSearch(e.target.value);
});場景2:文件上傳取消
classFileUploader{
constructor() {
this.controller = null;
}
async upload(file) {
// 取消正在進行的上傳
if (this.controller) {
this.controller.abort();
}
this.controller = new AbortController();
const formData = new FormData();
formData.append('file', file);
try {
const response = await axios.post('/api/upload', formData, {
signal: this.controller.signal,
onUploadProgress: (progressEvent) => {
const percent = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
updateProgress(percent);
}
});
return response.data;
} catch (error) {
if (error.message !== 'canceled') {
throw error;
}
}
}
cancel() {
if (this.controller) {
this.controller.abort();
}
}
}場景3:頁面切換取消請求
classPageRequestManager{
constructor() {
this.controllers = new Map();
}
addRequest(requestId, controller) {
this.controllers.set(requestId, controller);
}
cancelRequest(requestId) {
const controller = this.controllers.get(requestId);
if (controller) {
controller.abort();
this.controllers.delete(requestId);
}
}
cancelAll() {
for (const controller of this.controllers.values()) {
controller.abort();
}
this.controllers.clear();
}
}
// 在vue組件中使用
export default {
data() {
return {
requestManager: new PageRequestManager()
};
},
beforeUnmount() {
// 組件銷毀時取消所有請求
this.requestManager.cancelAll();
},
methods: {
async loadData() {
const controller = new AbortController();
const requestId = 'user-data';
this.requestManager.addRequest(requestId, controller);
try {
const response = await axios.get('/api/user', {
signal: controller.signal
});
this.userData = response.data;
} catch (error) {
if (error.message !== 'canceled') {
this.handleError(error);
}
} finally {
this.requestManager.cancelRequest(requestId);
}
}
}
};注意事項
- 錯誤處理:取消請求會觸發錯誤,需要正確區分是取消錯誤還是其他錯誤。
- 狀態清理:取消請求后要及時清理相關狀態,比如上傳進度、加載狀態等。
- 用戶體驗:給用戶明確的反饋,比如顯示"已取消"的提示。
- 內存管理:及時移除事件監聽器,避免內存泄漏。
最佳實踐
- 統一管理所有可取消的請求
- 在組件銷毀時自動取消相關請求
- 為用戶提供明確的取消反饋
- 在合適的場景使用取消功能,不要過度使用
取消HTTP請求是一個很實用的功能,正確使用可以顯著提升應用性能和用戶體驗。希望這些示例能幫助你在實際項目中更好地應用這個功能。































