1.AbortController 底层原理
- AbortController 是Web API标准(whatwg),在浏览器和Node.js 15+都可用。
- 内部维护一个 AbortSignal 实例,该实例有一个标志 aborted 和一个回调列表(监听abort事件)。
- 调用 controller.abort() 时,将 signal.aborted 设为 true,并通过触发所有通过 signal.addEventListener("abort") 注册的回调。
- 支持自定义异步任务取消:在异步操作内监听 signal.abort 事件,主动中断操作(例如清理定时器、拒绝Promise)。
2.广泛使用场景
- Fetch请求取消:避免过期请求返回后,覆盖最新数据。
- 事件 监听器 清理:使用 signal 一次性移除多个监听事件。
- 防抖/节流增强:可取消延迟执行。
- 异步队列任务:取消未开始的批量任务。
- React 组件卸载时:取消所有进行中的请求。
3.深度代码示例:
3.1 示例:取消fetch请求
ini
const controller = new AbortController();
const { signal } = controller;
fetch('/api/data', { signal })
.then(res => res.json())
.catch(err => {
if(err.name === 'AbortError') console.log('请求已取消');
})
// 延迟100ms后取消
setTimeout(() => controller.abort(), 100);
3.2 示例2:同时取消多个异步任务(使用同一个signal)
javascript
function cancellableDelay(ms, signal) {
return new Promise((resolve, reject) => {
const timer = setTimeout(resolve, ms);
if(signal) {
signal.addEventListener('abort', () => {
clearTimeout(timer);
reject(new DOMException('Aborted', 'AbortError'));
})
}
});
}
const controller = new AbortController();
cancellableDelay(1000, controller.signal).catch(e => console.log(e.name));
setTimeout(() => controller.abort(), 500); // 500ms后取消
3.3 示例3:封装支持取消的异步迭代器(生成器)
javascript
async function* cancellableGenerator(iterable, signal) {
for(const item of iterable) {
if(signal.aborted) throw new Error("Generator aborted");
// 模拟异步处理
await new Promise(resolve => setTimeout(resolve, 100));
yield item;
}
}
const controller = new AbortController();
const gen = cancellableGenerator([1,2,3,4,5], controller.signal);
(async () => {
for await (const val of gen) {
console.log(val);
if(val === 3) {
controller.abort();
}
}
})();
4. 项目实战:实现一个支持取消、超时、重试的通用请求函数
需求:封装 fetchWithRetry,支持取消(AbortController)、超时自动取消、失败重试(指数退避)。
javascript
async function fetchWithRetry(url, options = {}) {
const { timeout = 5000, maxRetries = 3, retryDelay = 1000, ...fetchOptions } = options;
let lastError;
const outerController = new AbortController();
// 超时定时器
const timeoutId = setTimeout(() => outerController.abort(), timeout);
for(let attempt = 0; attempt <= maxRetries; attempt++) {
const controller = new AbortController();
const signal = controller.signal;
// 父级取消时,子请求也取消
outerController.signal.addEventListener('abort', () => controller.abort());
try {
const response = await fetch(url, { ...fetchOptions, signal });
clearTimeout(timeoutId);
if(!response.ok) throw new Error(`HTTP ${response.status}`);
return response;
} catch (err) {
lastError = err;
if(err.name === 'AbortError'){
// 超时或主动取消不再重试
throw new Error('Request aborted or timeout');
}
if (attempt === maxRetries) break;
// 指数退避延迟
const delay = retryDelay * Math.pow(2, attempt);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw lastError;
}
// 使用
const controller = new AbortController();
fetchWithRetry('https://api.example.com/data', { timeout: 2000 })
.then(res => res.json())
.catch(e => console.error(e));
// 可手动取消
// controller.abort();
5. 大厂面试官追问
Q1: AbortController 能否取消正在执行的Promise链?例如已经then注册的回调?
不能直接取消已经开始执行的Promise链(因为Promise一旦创建就无法撤销)。但是可以在每个then回调中检查signal.aborted并提前返回或throw。更优雅的方式是使用Promise.race配合reject信号。
Q2: 如何实现一个可取消的 setTimeout 但不用 clearTimeout,而是用Promise?
利用Promise.race
javascript
function cancellableDelay(ms, signal) {
const delayPromise = new Promise(resolve => setTimeout(resolve, ms));
const abortPromise = new Promise((_, reject) => {
if(signal) signal.addEventListener('abort', () => reject(new Error('aborted')));
});
return Promise.race([delayPromise, abortPromise]);
}
Q3: AbortSignal 可以实现超时自动触发吗?Node.js 中的 AbortSignal.timeout() 是什么?
可以!Node.js 17.3+ 支持 AbortSignal.timeout(ms),返回一个在指定毫秒后自动 abort 的 signal。浏览器中可使用 AbortSignal.timeout (较新版本)。手动实现:new AbortController() + setTimeout(() => controller.abort(), ms)。
Q4: 如何使用 AbortController 取消一个正在进行的WebSocket连接?
标准的 WebSocket API 没有原生支持 AbortController,但是可以包装:在abort事件中调用 ws.close() 并reject。或者使用ws库(Node.js)支持signal选项。
6.API跨环境适用性总结
| API | 浏览器 | Node.js | 通用 | 实际使用场景 |
|---|---|---|---|---|
| queueMicrotask | ✅ | ✅ | ✅ | 安全地将任务放入微任务队列,避免 Promise 开销。适用于库中需要异步但优先级高的操作。 |
| MessageChannel | ✅ | ✅ (需 require('worker_threads').MessageChannel 或全局?Node 16+ 支持 MessageChannel 全局) |
部分 | 跨线程/跨 iframe/跨 Worker 通信。在微前端或复杂 Web Worker 通信中非常有用。 |
| requestIdleCallback | ✅ | ❌ (可用 setImmediate 或第三方 idle-gc) |
否 | 浏览器中执行低优先级后台任务(如数据预加载、埋点上报),避免影响动画。 |
| requestAnimationFrame | ✅ | ❌ | 否 | 浏览器动画、批量 DOM 更新、帧率控制。 |
| setImmediate | ❌ (非标准) | ✅ | 否 | Node.js 中在当前事件循环的 check 阶段立即执行,比 setTimeout(fn,0) 快。 |
| process.nextTick | ❌ | ✅ | 否 | Node.js 中在同一阶段内优先执行的任务,用于错误处理或继续执行。 |
| AbortController | ✅ | ✅ (Node 15+) | ✅ | 取消 fetch、自定义异步任务、事件监听器批量移除。 |
6.1实际应用案例:
queueMicrotask:在Vue或者React的nextTick类似实现中,用于在DOM更新后执行回调。MessageChannel:React调度器(Scheduler)中使用MessageChannel产生宏任务,以不延迟的方法实现高优先级更新。requestIdleCallback:Google Analytics在空闲时发送数据;React的fiber并发模式曾经依赖他(现用MessageChannel)。requestAnimationFrame:滚动视差动画、Canvas游戏循环、高性能拖拽。setImmediate(Node.js):在递归调用中替代setTimeout(fn,0)避免延迟钳制。process.nextTick:用于确保事件循环继续之前执行某些操作,例如EventEmitter的错误处理。