fetch无原生timeout参数,必须用AbortController+Promise.race实现真超时:新建controller、传signal、设setTimeout abort、race竞争出口,并检查err.name==='AbortError'。
HTML 本身无法处理 fetch 超时——form 标签没有超时属性,fetch() 本身也不认 timeout: 5000 这种写法。真正可行的路径只有一条:用 JavaScript 拦截表单、手动生成 fetch 请求,并通过 AbortController 主动中止。
fetch(url, { timeout: 5000 }) 完全无效这不是浏览器 bug,而是规范没定义这个字段。Chrome、Firefox、Safari 都会静默忽略它,请求照常挂起,直到底层 TCP 超时(通常 2–5 分钟),用户卡在白屏却无反馈。你看到的“超时错误”大概率是服务端返回的 408 或 504,和前端控制无关。
timeout 不在 Fetch API 规范里,任何教程里出现这个写法,要么过时,要么误导setTimeout(() => reject(new Error())) 无法终止网络请求,后续响应仍可能触发 then 或 catch,引发竞态问题AbortController + Promise.race 实现真超时这是目前唯一能真正中断进行中请求、且被 Chrome 66+ / Firefox 57+ / Safari 12.2+ 广泛支持的方式。核心不是“等超时”,而是“发信号让 fetch 主动退出”。
AbortController 实例,复用会导致多个请求被同一个 abort() 意外中止signal: controller.signal 必须传进 fetch 的 options,缺了就不起作用Promise.race([fetch(...), timeoutPromise]) 控制出口,避免 await fetch 卡死主线程catch 里要检查 err.name === 'AbortError',不能只看 err.message 是否含 “timeout”function fetchWithTimeout(url, options = {}, timeout = 8000) { const controller = new AbortController(); const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Request timeout')), timeout) ); return Promise.race([ fetch(url, { ...options, signal: controller.signal }), timeoutPromise ]);}
用 fetch 替代原生表单提交时,超时逻辑极易崩坏,尤其在用户快速连点或页面跳转时。
立即学习“前端免费学习笔记(深入)”;
event.preventDefault():表单仍会走传统提交路径,导致页面跳转 + fetch 双重发送submit 事件里新建 AbortController:复用旧实例会让后续请求被前一次超时误杀controller.abort() 可能来不及执行;可加 window.addEventListener('beforeunload', () => controller.abort()) 补一刀(注意该事件中不能 await)AbortController 在 IE 中完全不可用,Android 4.x WebView 也基本不支持。此时只能放弃“真正中止请求”,退为“逻辑超时”:
Promise.race 包裹 fetch 和定时器,超时后拒绝 Promise,但已发出的请求仍在后台跑proxy_read_timeout 至少设 10s,留出网络抖动余量真正难的不是写那几行 AbortController 代码,而是每次请求都记得新建实例、每次 catch 都检查 AbortError、每次表单提交都确认 preventDefault —— 这些细节一漏,超时逻辑就形同虚设。