← 返回题目列表

Fetch 和 XMLHttpRequest 有什么区别?项目中如何选择?

高频 中等 第 12 / 26 题 更新于 2026/07/29
FetchXMLHttpRequest请求封装前端网络

简化版

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 更先进”,要说清错误处理和能力边界。

完整版教学

一、先看两者定位

维度FetchXMLHttpRequest
API 风格Promise事件回调
错误语义HTTP 错误不自动 reject通过事件与状态码判断
上传进度原生支持不完整xhr.upload.onprogress 成熟
取消请求AbortControllerxhr.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,这基本就是面试官最想听的边界。