必须用 XMLHttpRequest 的场景有五种:需监听上传进度、需中途中断请求、运行在 IE11 或更早环境、需手动设置 Content-Length 等精细请求头、需复用底层连接;fetch 默认不报 HTTP 错误、不带 cookie、无超时、body 只读、JSON 请求需手动设头,且服务端非 JSON 响应前须校验 content-type。

现代 JavaScript 发起网络请求,fetch 是首选,XMLHttpRequest(XHR)仅在需兼容极老环境或特殊控制场景下才用。
什么时候必须用 XMLHttpRequest
不是“推荐”,而是“别无选择”:
- 需要监听上传进度(
xhr.upload.onprogress),fetch原生不支持上传进度事件(虽可用ReadableStream+TransformStream拦截,但复杂且兼容性差) - 需中途中断请求(
xhr.abort()),而fetch的取消依赖AbortController,旧版 Safari(≤12.1)不支持 - 运行在 IE11 或更早环境(
fetch完全不可用,必须用 XHR) - 需要精细控制请求头(如设置
Content-Length手动值)或复用底层连接(XHR 允许调用open(..., false)同步模式,尽管已废弃)
fetch 常见陷阱与绕过方式
fetch 表面简洁,但默认行为和错误处理容易误判:
-
fetch只在网络失败时 reject(如 DNS 错、断网),HTTP 状态码 404/500 不触发catch,必须手动检查response.ok或response.status - 默认不带 cookie,需显式传
{ credentials: 'include' },否则登录态丢失 - 没有超时控制,需配合
AbortController实现:const controller = new AbortController(); setTimeout(() => controller.abort(), 5000); fetch('/api/data', { signal: controller.signal }) -
Response.body是只读流,只能读一次;重复调用json()或text()会报错TypeError: body stream is locked
POST 请求:fetch 和 XHR 的参数差异
同样发 JSON,写法和隐含行为不同:
立即学习“Java免费学习笔记(深入)”;
-
fetch要求手动设Content-Type头,否则后端可能解析失败:fetch('/api/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ user: 'a', pass: 'b' }) }) - XHR 默认不设任何头,需
xhr.setRequestHeader();且send()接收字符串、FormData或Blob,不能直接传对象 - 若用
FormData(如文件上传),fetch无需设Content-Type(浏览器自动设为multipart/form-data; boundary=...),XHR 同样如此,但 XHR 需注意不要手动覆盖该头
兼容性与 polyfill 策略
不建议全局 polyfill fetch,尤其在已有 XHR 封装的旧项目里:
-
whatwg-fetchpolyfill 无法修复AbortController缺失问题,超时逻辑仍需降级为 XHR - 若项目已用 Axios,它内部对低版本浏览器自动回退到 XHR,比手动维护两套逻辑更可靠
- 真正要兼容 IE11?直接统一用 XHR 封装一层,避免在业务代码里混用两种 API,减少条件分支和测试成本
最易被忽略的一点:服务端返回非 JSON 内容(比如纯文本或空响应体)时,response.json() 会直接抛错,连 response.status 都来不及检查——务必先 if (response.headers.get('content-type')?.includes('application/json')) 再解析。










