Fetch 不能直接替代 XMLHttpRequest,因二者在错误处理、上传进度、凭据携带、请求取消等核心机制上存在本质差异,混用将导致调试成本剧增。

Fetch 不能直接替代 XMLHttpRequest,两者适用场景不同;选错一个,轻则进度条做不出来、登录态失效,重则 Safari 白屏、IE 直接报错。
fetch 不会因 401/404/500 报错,必须手动检查 response.ok
这是最常被忽略的逻辑断层:fetch 只在网络级失败(如断网、CORS 拒绝、DNS 失败)时 reject Promise;而所有 HTTP 状态码,包括 401、404、500,它都 resolve 并返回一个 Response 对象。
常见错误现象:
- 写了
fetch('/api/profile').then(r => r.json()),结果 401 时控制台没报错,但r.json()解析失败或返回空对象 - 没加
if (!response.ok) throw new Error(),等于默认把所有状态码当“成功”处理
XMLHttpRequest 则相反:onload 总是触发,你必须自己写 xhr.status === 200 或 in [200, 201] 才能判断业务是否成功。
立即学习“前端免费学习笔记(深入)”;
上传大文件时,xhr.upload.onprogress 不可替代
如果要显示实时上传进度条(比如已传 3.2MB / 总 128MB),XMLHttpRequest 是目前唯一原生支持的方案。
原因很实在:
-
XMLHttpRequest提供xhr.upload.onprogress,能直接拿到event.loaded和event.total -
fetch()原生不暴露上传过程;想模拟进度得靠ReadableStream+ 分块读取 + 手动上报,代码复杂、兼容性差(Safari 16.4 以下不支持 TransformStream) - 即使你用
FormData发送大文件,fetch 也只在全部发完后才 resolve,中间完全黑盒
凭据(Cookie)默认行为不一致,跨域时极易 401
同域下,XMLHttpRequest 默认携带 Cookie;fetch 默认等价于 { credentials: 'omit' } —— 这意味着,把 axios 换成原生 fetch,几乎必然触发登录态丢失。
修复方式明确但必须显式写:
- 需要 Cookie 时:
fetch(url, { credentials: 'include' }) - 跨域时,后端必须响应:
Access-Control-Allow-Origin为具体域名(不能是*),且带Access-Control-Allow-Credentials: true - 对应地,
XMLHttpRequest要设xhr.withCredentials = true
漏掉任一条件,请求就静默不带 Cookie,服务端看到的就是未登录用户。
AbortController 与 xhr.abort() 的取消语义完全不同
两者都能取消请求,但底层行为差异影响很大:
-
xhr.abort()是粗暴终止 TCP 连接,可能留下半开连接或服务端资源未释放 -
AbortController发送的是“取消信号”,fetch 接收到后停止消费流、不再解析响应体,但连接可能继续完成(取决于浏览器实现) - 超时控制上:
XMLHttpRequest有原生xhr.timeout和ontimeout;fetch必须靠AbortController配合setTimeout实现
真正容易被忽略的不是“哪个更新”,而是混用两者时,错误分类逻辑不一致——比如把 404 当网络错误 catch,或在同一个项目里对凭据、取消、进度用两套判断标准,调试成本会指数上升。



















