使用 promise.race 将业务逻辑与超时定时器竞争,并在超时发生时通过 abortcontroller 或自定义标记中断底层操作。具体做法是创建一个包装函数,内部启动一个拒绝型 promise 作为超时信号,同时监听主任务的完成或终止事件,确保资源不被泄漏。

如何编写通用的超时包装器
定义一个名为 withTimeout 的高阶函数,接收目标 Promise 和毫秒数作为参数。该函数利用 Promise.race 机制,让主任务与一个基于 setTimeout 的拒绝型 Promise 进行竞速。一旦定时器触发,Race 立即返回并抛出错误,此时需要清理可能仍在后台运行的异步任务。
function withTimeout(promise, ms) {
const controller = new AbortController();
const signal = controller.signal;
const timeout = new Promise((_, reject) => {
const id = setTimeout(() => {
reject(new Error('Operation timed out'));
}, ms);
signal.addEventListener('abort', () => clearTimeout(id));
});
return Promise.race([promise, timeout]).catch(err => {
if (err.message === 'Operation timed out') {
controller.abort();
}
throw err;
});
}
调用时传入带有 signal 支持的任务,例如 fetch 或自定义的可取消 Promise。若任务不支持信号,则需手动维护一个 isCancelled 标志位并在关键步骤检查。
如何处理非标准 Promise 的取消逻辑
对于不支持 AbortController 的传统异步操作,需要在任务内部轮询状态或提供显式的 cancel 方法。修改 withTimeout 以兼容这种模式,即在超时触发时调用取消接口,并等待其真正释放资源后再拒绝 Race。
function cancellableTask(signal) {
return new Promise((resolve, reject) => {
let cancelled = false;
// 模拟长时间计算
const interval = setInterval(() => {
if (cancelled) {
clearInterval(interval);
return;
}
// 执行部分逻辑
if (Math.random() > 0.9) resolve('Done');
}, 100);
signal.addEventListener('abort', () => {
cancelled = true;
clearInterval(interval);
reject(new Error('Manually cancelled'));
});
});
}
// 结合使用
const ctrl = new AbortController();
withTimeout(cancellableTask(ctrl.signal), 500).catch(console.error);
这种模式下,即使 Race 已经因为超时而“获胜”,内部的清理逻辑仍由 signal 驱动,保证了内存和定时器的正确回收。
超时后 Promise 的状态变化是什么
当超时发生时,Promise.race 返回的那个代表超时的 Promise 会进入 rejected 状态。这导致整个链式调用进入 catch 分支。此时原始的业务 Promise 可能仍处于 pending 状态,或者因为接收到 abort 信号而变为 rejected。关键在于,Race 的结果只取决于最先改变状态的 Promise,因此超时错误会覆盖业务结果。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











