javascript如何发起网络请求_fetch与XMLHttpRequest怎么选?

必须用 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.okresponse.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,写法和隐含行为不同:

  • fetch 要求手动设 Content-Type 头,否则后端可能解析失败:
    fetch('/api/login', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ user: 'a', pass: 'b' })
    })
  • XHR 默认不设任何头,需 xhr.setRequestHeader();且 send() 接收字符串、FormDataBlob,不能直接传对象
  • 若用 FormData(如文件上传),fetch 无需设 Content-Type(浏览器自动设为 multipart/form-data; boundary=...),XHR 同样如此,但 XHR 需注意不要手动覆盖该头

兼容性与 polyfill 策略

不建议全局 polyfill fetch,尤其在已有 XHR 封装的旧项目里:

  • whatwg-fetch polyfill 无法修复 AbortController 缺失问题,超时逻辑仍需降级为 XHR
  • 若项目已用 Axios,它内部对低版本浏览器自动回退到 XHR,比手动维护两套逻辑更可靠
  • 真正要兼容 IE11?直接统一用 XHR 封装一层,避免在业务代码里混用两种 API,减少条件分支和测试成本

最易被忽略的一点:服务端返回非 JSON 内容(比如纯文本或空响应体)时,response.json() 会直接抛错,连 response.status 都来不及检查——务必先 if (response.headers.get('content-type')?.includes('application/json')) 再解析。