在 async 函数中实现超时错误处理的核心是用 promise.race() 将目标异步操作与定时 reject 的 promise 竞速,超时则抛出错误;推荐结合 abortcontroller 真正中止 fetch 请求,并注意业务场景中的边界情况。

在 async 函数中实现超时错误处理,核心是用 Promise.race() 将目标异步操作与一个定时拒绝的 Promise 竞速,一旦超时就抛出错误,从而中断或捕获异常。
用 Promise.race 实现基础超时包装
这是最常用、最直观的方式:把原始 Promise 和一个延迟 reject 的 Promise 一起传给 Promise.race()。谁先完成(fulfill 或 reject),就以它的结果为准。
- 创建一个封装函数,接收原始 Promise 和超时毫秒数
- 构造一个
setTimeout触发的 reject Promise(注意用new Promise包裹,避免立即执行) - 用
race竞速,超时则抛出new Error('Timeout')
示例:
function timeout(promise, ms) {
return Promise.race([
promise,
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Operation timed out')), ms)
)
]);
}
// 使用
async function fetchData() {
try {
const result = await timeout(fetch('/api/data'), 5000);
return result.json();
} catch (err) {
if (err.message === 'Operation timed out') {
console.error('请求超时');
throw err;
}
throw err; // 其他错误照常抛出
}
}
封装成可复用的装饰器或工具函数
把超时逻辑抽离成独立函数,便于在多个 async 操作中复用,也利于统一错误类型和消息格式。
- 支持自定义错误类(如
TimeoutError),方便instanceof判断 - 允许传入 abortSignal(配合
fetch的signal选项,真正中止网络请求) - 返回带元信息的对象(如是否因超时失败),便于日志或监控
增强版示例:
class TimeoutError extends Error {
constructor(message = 'Operation timed out') {
super(message);
this.name = 'TimeoutError';
}
}
function withTimeout(promise, ms, { signal } = {}) {
let controller;
if (signal && typeof AbortController !== 'undefined') {
controller = new AbortController();
signal.addEventListener('abort', () => controller.abort());
}
return Promise.race([
promise,
new Promise((_, reject) => {
const timer = setTimeout(() => {
if (controller) controller.abort();
reject(new TimeoutError(`Timeout after ${ms}ms`));
}, ms);
// 清理:promise 完成后清除定时器
promise.finally(() => clearTimeout(timer));
})
]);
}
结合 AbortController 真正取消请求(推荐用于 fetch)
Promise.race 只能“拒绝”Promise,但底层 HTTP 请求可能还在运行。对 fetch 等支持 AbortSignal 的 API,应主动传递 signal 实现物理中止。
- 创建
AbortController,把signal传给fetch - 超时回调里调用
controller.abort(),触发 fetch 的 reject - 这样既响应超时,又释放连接、停止数据传输
示例:
async function fetchWithTimeout(url, options = {}, ms = 5000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), ms);
try {
const response = await fetch(url, {
...options,
signal: controller.signal
});
clearTimeout(timeoutId);
return response;
} catch (err) {
clearTimeout(timeoutId);
if (err.name === 'AbortError') {
throw new TimeoutError(`Fetch timed out after ${ms}ms`);
}
throw err;
}
}
在实际业务中注意边界情况
超时不是万能的,需结合场景谨慎设计:
- 避免对已耗尽资源的操作重复设超时(如重试逻辑里,总超时应大于单次超时 × 重试次数)
- 数据库查询等长耗时操作,优先考虑服务端限流/语句超时,而非仅靠客户端等待
- UI 层需同步更新状态(如按钮禁用、加载动画),不能只等 await 返回才处理
- Node.js 中某些底层操作(如
fs.promises.readFile)不支持 signal,只能依赖 race
不复杂但容易忽略。











