标准答案

  1. XMLHttpRequest 是较早的请求 API,主要通过事件和回调处理状态变化,原生支持上传/下载进度、timeout、abort 等能力。
  2. fetch 基于 Promise,语法更简洁,和 Request、Response、Headers、ReadableStream、AbortController 等 Web API 配合更自然。
  3. fetch 只有在网络错误、CORS 阻止、请求取消等情况下才会 reject。HTTP 404、500 表示服务端已经返回响应,所以 Promise 会 fulfilled,只是 response.ok 为 false。
  4. 业务代码使用 fetch 时要主动检查 response.ok 或 status,再决定是否抛错。还要注意 response.json()、text()、blob() 读取响应体本身也是异步操作。

题目解析

fetch 的 catch 不是 HTTP 状态处理器。只写 await fetch(url).catch(...) 不能捕获 500 这类业务失败,调用方仍需要检查 ok。

fetch 默认不携带跨源 Cookie,需要设置 credentials。服务端也要配合 CORS 凭证响应头,否则浏览器仍然不会把响应暴露给脚本。

XHR 在进度事件上仍有历史优势。需要上传进度时,很多项目仍会选择 XMLHttpRequest,或等待 fetch 上传进度相关能力的工程支持。

代码示例

下面封装会把 HTTP 错误状态转换成 rejected Promise,避免调用方漏判 response.ok。

JavaScript
async function requestJson(url, options = {}) {
  const response = await fetch(url, {
    credentials: 'include',
    ...options
  })

  if (!response.ok) {
    throw new Error('HTTP ' + response.status)
  }

  return response.json()
}

requestJson('/api/profile')
  .then((profile) => console.log(profile))
  .catch((error) => console.error(error.message))

常见误区

  • 误以为 HTTP 500 会自动进入 catch。
  • 忘记读取响应体需要调用 json/text/blob 等方法。
  • 跨域请求需要 Cookie 时忘记 credentials 和服务端 CORS 配置。

作者信息