推荐搭配 abortcontroller,因为 promise.race 仅忽略结果而不中断 fetch,abortcontroller 能真正中止网络请求、释放资源并避免流量浪费。

用 Promise.race 实现带超时的接口请求,核心思路是:把「真实请求」和「定时器拒绝」两个 Promise 一起传入 race,谁先完成就采用谁的结果——超时快就抛错,请求快就返回数据。
基本写法:封装一个 timeoutFetch
直接用 fetch + Promise.race 组合,最简实现如下:
function timeoutFetch(url, options = {}, timeout = 5000) {
const controller = new AbortController();
const { signal } = controller;
// 请求 Promise(支持原生 abort)
const request = fetch(url, { ...options, signal });
// 超时 Promise:到时间就 reject
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => {
controller.abort(); // 主动中止 fetch
reject(new Error('Request timed out'));
}, timeout);
});
return Promise.race([request, timeoutPromise]);
}
// 使用示例
timeoutFetch('/api/data', {}, 3000)
.then(res => res.json())
.then(data => console.log(data))
.catch(err => console.error('失败:', err.message));
为什么推荐搭配 AbortController?
单纯靠 Promise.race 不会取消正在运行的 fetch 请求,只是忽略结果。加上 AbortController 才能真正中断网络请求,避免资源浪费:
- 不 abort 的问题:即使超时了,fetch 仍在后台下载响应体,可能耗流量、占连接
-
abort 的好处:触发
fetch抛出AbortError,且浏览器会中止 TCP 连接或丢弃后续数据 - 注意:
controller.abort()必须在timeoutPromise的setTimeout中调用,不能只靠 race 返回就认为请求已停
处理常见边界情况
实际项目中还需考虑这些细节:
-
网络异常也应归入超时逻辑:比如 DNS 失败、连接被拒,它们本身就会快速 reject,无需额外处理;但若想统一错误类型,可在 catch 中判断并重抛
TimeoutError -
避免重复 abort:如果请求本就很快完成,
setTimeout里的controller.abort()仍会执行,但现代浏览器对此是安全的(多次 abort 无副作用) -
兼容老浏览器:若需支持不带
AbortController的环境(如 IE),可改用Promise.reject+ 手动标记 + 忽略后续 resolve,但无法真正中断请求
进阶:封装成可复用的工具函数
可以进一步抽象为通用超时包装器,不限于 fetch:
function withTimeout(promise, ms) {
const controller = new AbortController();
let cleanup = () => {};
const timeoutPromise = new Promise((_, reject) => {
const timer = setTimeout(() => {
controller.abort();
reject(new Error(`Operation timed out after ${ms}ms`));
}, ms);
// 清理函数,防止内存泄漏
cleanup = () => clearTimeout(timer);
});
// promise 完成后清理定时器
promise.finally(cleanup);
return Promise.race([promise, timeoutPromise]);
}
// 用法示例
withTimeout(fetch('/api/data'), 4000)
.then(r => r.json())
.catch(console.error);
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











