【Web API系列】XMLHttpRequest API和Fetch API深入理解与应用指南


前言

在现代Web开发中,客户端与服务器之间的异步通信是构建动态应用的核心能力。无论是传统的AJAX技术(基于XMLHttpRequest)还是现代的Fetch API,它们都为实现这一目标提供了关键支持。本文将从底层原理、核心功能、代码实践到实际应用场景,系统性地对比和分析这两种API的异同,帮助你掌握如何在不同场景下选择最佳解决方案。


文章目录

  • 前言
  • [一、XMLHttpRequest API:传统异步请求的基石](#一、XMLHttpRequest API:传统异步请求的基石)
    • [1.1 核心概念与工作原理](#1.1 核心概念与工作原理)
    • [1.2 关键功能与高级用法](#1.2 关键功能与高级用法)
    • [1.3 局限性分析](#1.3 局限性分析)
  • [二、Fetch API:现代Web请求的标准方案](#二、Fetch API:现代Web请求的标准方案)
    • [2.1 设计理念与核心优势](#2.1 设计理念与核心优势)
    • [2.2 基础用法与代码示例](#2.2 基础用法与代码示例)
    • [2.3 高级功能实践](#2.3 高级功能实践)
    • [2.4 常见问题与解决方案](#2.4 常见问题与解决方案)
  • [三、XMLHttpRequest与Fetch API的深度对比](#三、XMLHttpRequest与Fetch API的深度对比)
    • [3.1 功能特性对比](#3.1 功能特性对比)
    • [3.2 性能与适用场景](#3.2 性能与适用场景)
  • 四、实战案例:构建一个健壮的HTTP客户端
    • [4.1 基于Fetch的封装库](#4.1 基于Fetch的封装库)
    • [4.2 实现文件分片上传](#4.2 实现文件分片上传)
  • 总结

一、XMLHttpRequest API:传统异步请求的基石

1.1 核心概念与工作原理

XMLHttpRequest(XHR)是浏览器提供的原生API,用于在不刷新页面的情况下与服务器交换数据。其核心流程包括:

  1. 实例化对象 :通过构造函数创建XMLHttpRequest实例。
  2. 配置请求:指定HTTP方法、URL及是否异步。
  3. 绑定事件 :监听请求状态变化(如onloadonerror)。
  4. 发送请求 :调用send()方法并处理响应数据。
javascript 复制代码
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data', true);
xhr.onload = function() {
  if (xhr.status === 200) {
    console.log(JSON.parse(xhr.responseText));
  } else {
    console.error('请求失败:', xhr.status);
  }
};
xhr.onerror = function() {
  console.error('网络错误');
};
xhr.send();

1.2 关键功能与高级用法

  • 同步与异步模式

    javascript 复制代码
    // 同步请求(阻塞主线程,不推荐)
    xhr.open('GET', 'https://api.example.com/data', false);
    xhr.send();
    console.log(xhr.responseText);
  • 处理二进制数据

    javascript 复制代码
    xhr.responseType = 'arraybuffer';
    xhr.onload = function() {
      const buffer = xhr.response;
      // 处理二进制数据(如图像或文件)
    };
  • 上传进度监控

    javascript 复制代码
    xhr.upload.onprogress = function(event) {
      const percent = (event.loaded / event.total) * 100;
      console.log(`上传进度: ${percent}%`);
    };
  • 超时控制

    javascript 复制代码
    xhr.timeout = 5000; // 5秒超时
    xhr.ontimeout = function() {
      console.error('请求超时');
    };

1.3 局限性分析

  1. 回调地狱:事件监听机制导致代码嵌套复杂。
  2. 错误处理不统一:需手动检查HTTP状态码和网络错误。
  3. 不支持Promise:与现代异步编程模式不兼容。

二、Fetch API:现代Web请求的标准方案

2.1 设计理念与核心优势

Fetch API基于Promise设计,提供更简洁、灵活的请求方式,并天然支持以下特性:

  • 链式调用:避免回调嵌套。
  • Streams API集成:处理流式数据(如大文件下载)。
  • CORS与安全策略:默认不发送跨域Cookie,需显式配置。

2.2 基础用法与代码示例

javascript 复制代码
fetch('https://api.example.com/data')
  .then(response => {
    if (!response.ok) throw new Error('HTTP错误');
    return response.json();
  })
  .then(data => console.log(data))
  .catch(error => console.error('请求失败:', error));

2.3 高级功能实践

  • 自定义请求头与模式

    javascript 复制代码
    fetch('https://api.example.com/data', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer token'
      },
      body: JSON.stringify({ key: 'value' }),
      mode: 'cors', // 跨域模式
      credentials: 'include' // 包含Cookie
    });
  • 中断请求(AbortController)

    javascript 复制代码
    const controller = new AbortController();
    setTimeout(() => controller.abort(), 5000);
    
    fetch('https://api.example.com/data', {
      signal: controller.signal
    }).catch(error => {
      if (error.name === 'AbortError') {
        console.log('请求被手动取消');
      }
    });
  • 流式数据处理

    javascript 复制代码
    fetch('https://api.example.com/large-file')
      .then(response => {
        const reader = response.body.getReader();
        return new ReadableStream({
          start(controller) {
            function push() {
              reader.read().then(({ done, value }) => {
                if (done) {
                  controller.close();
                  return;
                }
                controller.enqueue(value);
                push();
              });
            }
            push();
          }
        });
      })
      .then(stream => new Response(stream))
      .then(response => response.blob());

2.4 常见问题与解决方案

  • 错误处理优化

    javascript 复制代码
    async function fetchWithRetry(url, retries = 3) {
      for (let i = 0; i < retries; i++) {
        try {
          const response = await fetch(url);
          if (!response.ok) throw new Error('HTTP错误');
          return await response.json();
        } catch (error) {
          if (i === retries - 1) throw error;
          await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
        }
      }
    }
  • 超时封装

    javascript 复制代码
    function fetchWithTimeout(url, timeout = 5000) {
      return Promise.race([
        fetch(url),
        new Promise((_, reject) =>
          setTimeout(() => reject(new Error('请求超时')), timeout)
        )
      ]);
    }

三、XMLHttpRequest与Fetch API的深度对比

3.1 功能特性对比

特性 XMLHttpRequest Fetch API
Promise支持
请求取消 是(abort()) 是(AbortController)
流式数据处理 有限支持 完全支持
CORS处理 需手动配置 默认安全策略
Service Worker 不支持 完全集成

3.2 性能与适用场景

  • XMLHttpRequest适用场景

    • 需要监控上传/下载进度。
    • 兼容旧版浏览器(如IE10及以下)。
  • Fetch API推荐场景

    • 现代Web应用开发。
    • 需要与Service Worker配合实现离线缓存。
    • 处理流式数据或大文件。

四、实战案例:构建一个健壮的HTTP客户端

4.1 基于Fetch的封装库

javascript 复制代码
class HttpClient {
  constructor(baseURL, headers = {}) {
    this.baseURL = baseURL;
    this.headers = headers;
  }

  async request(endpoint, options = {}) {
    const url = `${this.baseURL}${endpoint}`;
    const response = await fetch(url, {
      ...options,
      headers: { ...this.headers, ...options.headers }
    });

    if (!response.ok) {
      const error = new Error(`HTTP ${response.status}`);
      error.response = response;
      throw error;
    }

    const contentType = response.headers.get('content-type');
    if (contentType?.includes('application/json')) {
      return response.json();
    }
    return response.text();
  }

  get(endpoint, params) {
    const query = new URLSearchParams(params).toString();
    return this.request(`${endpoint}?${query}`, { method: 'GET' });
  }

  post(endpoint, body) {
    return this.request(endpoint, {
      method: 'POST',
      body: JSON.stringify(body),
      headers: { 'Content-Type': 'application/json' }
    });
  }
}

// 使用示例
const api = new HttpClient('https://api.example.com', {
  Authorization: 'Bearer token'
});
api.get('/users', { page: 1 }).then(users => console.log(users));

4.2 实现文件分片上传

javascript 复制代码
async function uploadFile(file, chunkSize = 1024 * 1024) {
  const totalChunks = Math.ceil(file.size / chunkSize);
  for (let i = 0; i < totalChunks; i++) {
    const start = i * chunkSize;
    const end = Math.min(start + chunkSize, file.size);
    const chunk = file.slice(start, end);

    const formData = new FormData();
    formData.append('file', chunk);
    formData.append('chunkIndex', i);
    formData.append('totalChunks', totalChunks);

    await fetch('/upload', {
      method: 'POST',
      body: formData
    });
  }
}

总结

XMLHttpRequest作为Web异步通信的奠基者,至今仍在特定场景下发挥作用,而Fetch API凭借其现代化的设计正在成为主流选择。开发者需要根据以下因素决策:

  1. 浏览器兼容性:如需支持旧版浏览器,XHR仍是必要选项。
  2. 功能需求:进度监控、请求取消等特性可能影响技术选型。
  3. 代码可维护性:Fetch的Promise链与async/await语法更易维护。

未来,随着Web Streams API和Service Worker的普及,Fetch API将在性能优化和离线体验领域展现更大潜力。建议在新项目中优先采用Fetch,同时保持对XHR原理的理解以应对遗留系统维护需求。

相关推荐
恋猫de小郭1 小时前
Android Studio Cloud 正式上线,不只是 Android,随时随地改 bug
android·前端·flutter
清岚_lxn5 小时前
原生SSE实现AI智能问答+Vue3前端打字机流效果
前端·javascript·人工智能·vue·ai问答
ZoeLandia6 小时前
Element UI 设置 el-table-column 宽度 width 为百分比无效
前端·ui·element-ui
橘子味的冰淇淋~6 小时前
解决 vite.config.ts 引入scss 预处理报错
前端·vue·scss
小小小小宇8 小时前
V8 引擎垃圾回收机制详解
前端
lauo8 小时前
智体知识库:ai-docs对分布式智体编程语言Poplang和javascript的语法的比较(知识库问答)
开发语言·前端·javascript·分布式·机器人·开源
拉不动的猪8 小时前
设计模式之------单例模式
前端·javascript·面试
一袋米扛几楼989 小时前
【React框架】什么是 Vite?如何使用vite自动生成react的目录?
前端·react.js·前端框架
Alt.99 小时前
SpringMVC基础二(RestFul、接收数据、视图跳转)
java·开发语言·前端·mvc
进取星辰9 小时前
1、从零搭建魔法工坊:React 19 新手村生存指南
前端·react.js·前端框架