Fetch 和 XMLHttpRequest 有什么区别?项目中如何选择?
简化版
fetch 是现代 Promise 风格网络 API,语法更简洁,天然适合 async/await 和流式响应;XMLHttpRequest 更老,但上传进度、兼容性和某些低版本环境支持更成熟。项目中一般优先用 fetch,遇到上传进度、老浏览器兼容或已有封装体系时再考虑 XHR。
详细版
核心区别可以从调用风格、错误语义、进度能力、取消方式和响应处理看。fetch 只有网络失败、CORS 拦截等才 reject,HTTP 404/500 不会自动 reject,需要检查 response.ok;XHR 则通过事件和 status 判断。
async function request(url) {
const response = await fetch(url)
if (!response.ok) throw new Error(`HTTP ${response.status}`)
return response.json()
}
fetch 支持 AbortController 取消请求,支持 ReadableStream 读取流式响应;XHR 支持 upload.onprogress,在上传文件进度条场景仍常见。面试回答不能只说“fetch 更先进”,要说清错误处理和能力边界。
完整版教学
一、先看两者定位
| 维度 | Fetch | XMLHttpRequest |
|---|---|---|
| API 风格 | Promise | 事件回调 |
| 错误语义 | HTTP 错误不自动 reject | 通过事件与状态码判断 |
| 上传进度 | 原生支持不完整 | xhr.upload.onprogress 成熟 |
| 取消请求 | AbortController | xhr.abort() |
| 流式响应 | 支持 ReadableStream | 能力较弱 |
XHR 是早期 AJAX 的核心 API,Fetch 是后来的现代替代方案。现代项目中,Fetch 的可读性、组合能力和标准化程度更好,但不代表它覆盖了 XHR 的所有工程场景。
记忆钩子:Fetch 像现代 Promise 请求器,XHR 像老牌事件请求器;选型看能力边界,不看名字新旧。
二、错误语义是最高频坑
fetch('/api/missing')
.then(response => console.log(response.status)) // 404 仍会进 then
.catch(error => console.log('network error', error))
fetch 只有请求无法完成时才 reject,例如网络断开、CORS 被浏览器拦截、Abort 取消。服务端返回 404、500 仍然是一次成功收到 HTTP 响应,所以 Promise 会 fulfilled。
可靠封装应显式检查:
async function getJson(url) {
const response = await fetch(url)
if (!response.ok) {
throw new Error(`HTTP ${response.status}`)
}
return response.json()
}
三、上传进度为什么常用 XHR
function upload(file) {
const xhr = new XMLHttpRequest()
xhr.open('POST', '/upload')
xhr.upload.onprogress = event => {
if (event.lengthComputable) {
console.log(Math.round(event.loaded / event.total * 100))
}
}
const form = new FormData()
form.append('file', file)
xhr.send(form)
}
假设上传 100MB 文件,用户需要看到 10%、50%、90% 这种进度反馈。XHR 的上传进度事件成熟直接;Fetch 对下载流更友好,但上传进度长期不是它的强项,虽然平台能力在演进,工程上仍常见 XHR 上传封装。
四、取消请求的方式不同
const controller = new AbortController()
fetch('/api/search?q=react', { signal: controller.signal })
.catch(error => {
if (error.name === 'AbortError') return
throw error
})
controller.abort()
在搜索联想、路由切换、组件卸载时,取消旧请求能减少无效响应覆盖新状态。Fetch 通过 AbortController 传播取消信号;XHR 通过实例方法 abort() 取消。两者都要在业务层处理取消后的错误或状态,不能让取消被误报成真正失败。
五、凭证和 CORS 配置
Fetch 默认不会在跨源请求中携带 Cookie,常见需要写:
fetch('https://api.example.com/user', {
credentials: 'include',
})
XHR 对应的是 xhr.withCredentials = true。跨源携带凭证还要求服务端返回精确 Access-Control-Allow-Origin,不能是 *,并且要允许 credentials。前端只改一个参数,服务端 CORS 不配合也不会成功。
六、流式响应和大数据读取
const response = await fetch('/stream')
const reader = response.body.getReader()
Fetch 的响应体可以是流,适合逐块读取大文件、SSE 替代方案或 AI 流式文本等场景。假设响应总量 20MB,流式读取能更早处理前 1MB,而不是等所有内容下载完再解析。
但流式处理会增加代码复杂度,并受浏览器、代理、压缩和服务端 flush 行为影响。面试中只要说明它是 Fetch 的重要现代能力,不必把流协议写满。
七、常见误区与追问
- 误区:Fetch 遇到 500 会自动进入 catch。 HTTP 错误不会自动 reject,要检查
response.ok。 - 误区:Fetch 完全替代 XHR。 上传进度和老项目兼容场景下 XHR 仍有价值。
- 误区:取消请求只是不处理响应。 真正取消应调用
AbortController.abort()或xhr.abort()。 - 追问:Fetch 跨域带 Cookie 怎么写? 设置
credentials: 'include',服务端也要允许凭证。 - 追问:为什么封装请求要统一处理状态码? 避免每个调用点忘记检查
ok,造成业务误判。 - 追问:Fetch 的流式响应有什么用? 可以边下载边处理,降低首块数据等待时间和内存峰值。
八、加强记忆
Fetch 和 XHR 的比较要抓住“语法、错误、进度、取消、凭证、流”六个点。一般项目优先 Fetch,因为它更贴近 Promise 和现代响应流;文件上传进度、老浏览器兼容、遗留封装则可能继续用 XHR。答题时特别补上 fetch 404 不 reject,这基本就是面试官最想听的边界。