手写一个 XMLHttpRequest 请求;XHR、fetch 与 axios 在进度、取消、错误判断上有什么区别?

先理解原理和示例,再练习完整口述与追问。

XHR 通过 open、setRequestHeader、send 发请求,onload 判断状态码,onerror 处理网络错误,支持上传进度。fetch 基于 Promise,只有网络失败才拒绝,HTTP 404/500 仍然 resolve,需要检查 response.ok,用 AbortController 取消,上传进度不方便。axios 在浏览器默认用 XHR 适配器,非 2xx 默认拒绝。

XHR 的 readyState 从 0 到 4 依次表示未打开、已打开、收到响应头、加载中、完成。onload 在收到完整响应时触发,不论状态码;网络中断、DNS 失败、CORS 被拒触发 onerror;timeout 超时触发 ontimeout;abort() 触发 onabort。xhr.upload.onprogress 提供上传进度,必须在 send 之前绑定。

fetch 返回 Promise,404/500 也是“成功收到响应”;response.body 是 ReadableStream,可以读取下载进度;没有原生超时,需要 AbortSignal.timeout() 或定时器配合 abort;跨域携带 Cookie 用 credentials: 'include'。

js
function request({ method = 'GET', url, headers = {}, body = null, timeout = 0, signal, onUploadProgress }) {
  return new Promise((resolve, reject) => {
    if (signal && signal.aborted) return reject(new DOMException('请求已取消', 'AbortError'))
    const xhr = new XMLHttpRequest()
    xhr.open(method, url)
    xhr.timeout = timeout
    Object.entries(headers).forEach(([name, value]) => xhr.setRequestHeader(name, value))
    xhr.onload = () => {
      const response = { status: xhr.status, body: xhr.responseText }
      if (xhr.status >= 200 && xhr.status < 300) resolve(response)
      else reject(Object.assign(new Error(`HTTP ${xhr.status}`), { response }))
    }
    xhr.onerror = () => reject(new Error('网络错误或跨域被拒绝'))
    xhr.ontimeout = () => reject(new Error('请求超时'))
    xhr.onabort = () => reject(new DOMException('请求已取消', 'AbortError'))
    if (onUploadProgress) xhr.upload.onprogress = onUploadProgress
    if (signal) signal.addEventListener('abort', () => xhr.abort(), { once: true })
    xhr.send(body)
  })
}

调用前已取消的 signal 直接拒绝;因为在 open 之后、send 之前调用 abort 不会触发 onabort,否则 Promise 会一直挂起。

CORS 失败时前端拿不到具体原因,只能看到网络错误,需要看控制台和服务端响应头;xhr.status 为 0 往往表示网络失败或被拦截。

XHR 是 open、设置请求头、send,再在 onload 里按状态码判断成功失败,onerror、ontimeout、onabort 分别处理网络错误、超时和取消,上传进度用 upload.onprogress。fetch 是 Promise 风格,但只有网络失败才拒绝,404 也会 resolve,要自己判断 ok;取消和超时都用 AbortController,上传进度不方便。axios 浏览器端默认基于 XHR,提供拦截器、非 2xx 自动拒绝和进度回调。