从 XHR 到 Fetch:前端异步请求的进化史 🚀 (附实战代码)

166 阅读3分钟

引言:为什么前端需要异步请求?🤔

在 Web 1.0 时代,页面更新全靠刷新 —— 用户点击按钮后,整个页面重新加载,体验像 “翻书” 一样笨重。而 Web 2.0 的核心突破,就是异步请求:让 JavaScript 在不刷新页面的情况下偷偷和后端交互,实现动态更新内容(比如刷朋友圈、加载评论)。

今天我们就来拆解前端异步请求的两大 “功臣”:早期的XMLHttpRequest(简称 XHR)和现代的Fetch API,看看它们如何一步步优化前端数据交互体验!

一、上古神器:XMLHttpRequest 详解 📜

1.1 什么是 XHR?

XMLHttpRequest是浏览器提供的原生 API,诞生于 2005 年左右,是 Ajax(异步 JavaScript 和 XML)技术的核心。它允许前端主动发起 HTTP 请求,彻底改变了 “刷新才更新” 的传统模式。

1.2 实战代码拆解

const getJSON = async (url) => {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest(); // 1. 创建XHR实例
    console.log(xhr.readyState); // 输出0:未初始化
    xhr.open('GET', 'https://api.github.com/users/shunwuyu/repos'); // 2. 配置请求
    console.log(xhr.readyState); // 输出1:已打开连接
    xhr.send(); // 3. 发送请求
    console.log(xhr.readyState); // 输出1:请求刚发送,未收到响应
    // 4. 监听状态变化
    xhr.onreadystatechange = function() {
      if (xhr.readyState === 4) { // 响应完全接收
        resolve(JSON.parse(xhr.responseText));
      }
    }
  })
}

1.3 核心知识点:XHR 的生命周期

readyState是 XHR 的灵魂,它的 5 种状态对应请求的完整流程:

  • 0:未初始化(刚创建实例,未调用 open)

  • 1:已打开(调用 open 后,未发送请求)

  • 2:已发送(调用 send 后,未收到响应头)

  • 3:接收中(收到部分响应体)

  • 4:完成(响应全部接收)

划重点:只有当readyState === 4时,才能安全地读取responseText

1.4 如何渲染数据?

通过 IIFE(立即执行函数)包裹异步逻辑,避免顶层await报错:

(async () => {
  const data = await getJSON(); // 等待请求完成
  // 动态生成列表
  document.getElementById('repos').innerHTML = 
    data.map(item => `<li>${item.name}</li>`).join('');
})()

二、现代方案:Fetch API 详解 ✨

2.1 Fetch 的诞生:为了解决什么问题?

XHR 的语法繁琐(需要手动监听状态、处理解析),而Fetch API在 ES6 后登场,基于 Promise 设计,配合async/await让异步代码像同步代码一样直观!

2.2 实战代码拆解

document.addEventListener('DOMContentLoaded', async () => {
  // 1. 发起请求(返回Promise)
  const result = await fetch('https://api.github.com/users/shunwuyu/repos');
  // 2. 解析响应(response.json()也是异步操作)
  const data = await result.json();
  // 3. 渲染数据
  document.getElementById('repos').innerHTML = 
    data.map(item => `<li>${item.name}</li>`).join('');
})

2.3 核心知识点:Fetch 的异步链条

  • 第一步fetch(url)返回一个 Promise,状态为pending

  • 第二步:当服务器响应时,Promise 变为fulfilled,得到Response对象(result

  • 第三步result.json()将响应体解析为 JSON,这也是一个 Promise,需再次await

注意:Fetch 只在网络错误时 reject,HTTP 404/500 等状态不会触发 reject,需手动判断:

if (!result.ok) throw new Error('请求失败');

三、XHR vs Fetch:全方位对比 🔍

特性XMLHttpRequestFetch API
语法回调地狱风险(需嵌套事件)支持 Promise 和 async/await,更简洁
错误处理需监听onerror事件仅网络错误 reject,HTTP 错误需手动处理
响应解析需手动 JSON.parse (responseText)内置 response.json () 方法
中止请求支持 abort () 方法需配合 AbortController
兼容性所有现代浏览器(包括 IE)IE 不支持,需 polyfill
数据流处理支持 Stream API,可处理大文件

四、实战总结:该选哪种方案? 🤷‍♂️

  • 选 XHR:需要兼容 IE,或需精细控制请求中止 / 超时

  • 选 Fetch:现代项目,追求简洁语法和 Promise 生态

  • 终极方案:使用 Axios 等封装库(基于 XHR 或 Fetch),兼顾易用性和兼容性

从 XHR 的 “步步为营” 到 Fetch 的 “一气呵成”,前端异步请求的进化史,也是开发者体验不断优化的历史。你在项目中更爱用哪种方案?欢迎在评论区交流~ 💬