AbortController:讓異步操作隨時說停就停
一、什么是 AbortController?
AbortController 是 JavaScript 在瀏覽器和部分 Node.js 環境中提供的全局類,用來中止正在進行或待完成的異步操作(如 fetch() 請求、事件監聽、可寫流、數據庫事務等)。通過它,我們可以在需要的時候自由地終止這些操作,避免不必要的開銷或冗長的等待。
1. 核心思路
創建一個 AbortController 實例后,可以通過它的 signal 屬性將中止信號傳遞給相應的 API。當調用 controller.abort() 時,所有使用該信號的操作都會收到中止通知,并根據設置的邏輯停止執行或拋出錯誤。
二、基礎用法
// 創建 AbortController 實例
const controller = new AbortController();// 拿到 signal,并可將其傳入需要可中止的 API
const signal = controller.signal;// 主動觸發中止
controller.abort();
1. 實例 signal 屬性
- signal 是一個 AbortSignal 實例,可被用于任何支持中止的 API(如 fetch()、事件監聽器等)
- 一旦 abort() 被調用,signal 就會觸發 abort 事件,標記為已中止
2. 實例 abort() 方法
- 調用后會讓該 signal 上監聽的所有異步操作同時中止
- 可選地,abort(reason) 可以傳遞一個原因或錯誤,供業務邏輯作更細粒度的處理
3. 監聽中止事件
controller.signal.addEventListener('abort', () => {// 在這里編寫中止后的邏輯,例如清理資源或提示用戶
});
三、實用場景與示例
1. 事件監聽器自動清理
我們可以在添加事件監聽器時將 signal 作為選項的一部分
一旦 abort() 被調用,會自動移除與該 signal 關聯的監聽器,從而簡化了取消事件監聽的流程
const controller = new AbortController();window.addEventListener('resize', () => {console.log('Window resized!');
}, { signal: controller.signal });// 調用 abort(),會自動移除 resize 監聽器
document.getElementById('but').onclick = () => {controller.abort();
}
1.1. 優勢
- 無需手動保存監聽器引用再調用 removeEventListener()
- 若有多個監聽器共用同一個 signal,只需一次 abort() 就能一并移除
2. 在 React 中的應用示例
useEffect(() => {const controller = new AbortController();window.addEventListener('resize', handleResize, { signal: controller.signal });window.addEventListener('hashchange', handleHashChange, { signal: controller.signal });window.addEventListener('storage', handleStorageChange, { signal: controller.signal });return () => {// 調用一次 abort(),所有監聽器全部被移除controller.abort();};
}, []);
3. 中止 fetch() 請求
fetch() 支持通過 signal 中止 HTTP 請求,是 AbortController 最常見的應用場景之一
async function uploadFile(file) {const controller = new AbortController();const responsePromise = fetch('/upload', {method: 'POST',body: file,signal: controller.signal,});return { responsePromise, controller };
}const { responsePromise, controller } = uploadFile(file);document.getElementById('but').onclick = () => {controller.abort();
}
- 一旦 abort 被觸發,fetch() 返回的 Promise 將被拒絕,后端也將收到掛斷請求(具體取決于實際網絡環境)
4. Node.js 中止 http 請求
在新版 Node.js 環境中(v16+),AbortSignal 同樣可以應用于內置的 http 或 https 模塊中,以取消請求或響應讀取。用法與瀏覽器環境基本一致
4.1. server.js
const http = require('http');const server = http.createServer((req, res) => {console.log('收到請求:', req.url);res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });// 模擬長時間響應:每 500ms 輸出一段文字,共輸出 50 次let count = 0;const intervalId = setInterval(() => {if (count < 50) {res.write(`數據塊 #${count}\n`);count++;} else {clearInterval(intervalId);res.end('響應全部完成\n');}}, 500);req.on('close', () => {console.log('客戶端連接關閉,停止發送數據');clearInterval(intervalId);});
});const PORT = 3000;
server.listen(PORT, () => {console.log(`服務器已啟動,監聽端口 ${PORT}`);
});
4.2. controller.js
const http = require('http');function makeAbortableRequest() {const controller = new AbortController();const { signal } = controller;const req = http.get('http://localhost:3000',{ signal },(res) => {console.log('已連接到服務器,響應狀態:', res.statusCode);res.on('data', (chunk) => {console.log('收到數據塊:', chunk.toString());});res.on('end', () => {console.log('響應結束');});});req.on('error', (err) => {console.error('請求錯誤:', err.message || err);});setTimeout(() => {console.log('5 秒時間到,準備中止請求...');controller.abort();}, 5000);
}makeAbortableRequest();
5. 終止流操作
- 可寫流
- 對于基于 WritableStream 或者更底層的寫操作,如果支持將 signal 與寫操作關聯,當 abort() 被調用時,即可停止寫入并執行清理
- 自定義可中止邏輯
- 在數據庫事務中,自行監聽 signal 的 abort 事件來回滾事務或拋出特定錯誤,都能讓流程變得更靈活
async function example() {const abortController = new AbortController();const stream = new WritableStream({write(chunk, sinkController) {console.log('正在寫入:', chunk);},close() {console.log('寫入完成');},abort(reason) {console.warn('寫操作被中止:', reason);}});const writer = stream.getWriter();let i = 1const inter = setInterval(async () => {await writer.write(`數據塊 ${i}`);i++}, 1000)window.currentAbortController = abortController;window.currentWriter = writer;document.getElementById('cancelButton').onclick = async () => {clearInterval(inter)if (window.currentAbortController && window.currentWriter) {console.log('點擊了取消寫操作按鈕');await window.currentWriter.abort('用戶主動終止寫入');window.currentAbortController.abort('用戶主動終止寫入');} else {console.log('沒有正在進行的寫操作');}};
}example();
四、兼容性
- 瀏覽器
- Node.js
Node.js v16 及以上原生支持在 fetch()、http/https 模塊中使用 signal
五、總結與擴展
1. 核心價值
- AbortController 讓我們可以更優雅地中止異步操作,不再依賴過時的回調或繁瑣的清理邏輯。
- 避免占用資源或等待無效請求,提升性能和用戶體驗。
2. 應用場景廣泛
- 事件監聽清理、fetch() 請求取消、Node.js HTTP 請求中止、數據庫事務可回滾……幾乎所有異步操作都能通過一個 signal 實現“一鍵叫停”。
3. 常見注意點
- 在代碼中使用多條 fetch() 時,務必區分好各自的 controller,或使用 AbortSignal.any() 合并控制。
- 對于中止錯誤,需要在 .catch() 或事件監聽中顯式處理,防止誤認為是網絡異常或其他錯誤。