Skip to content

AJAX进度监控

一、AJAX进度监控

1、Ajax 的两种原生实现:XHR vs Fetch

Ajax(异步请求)的两种核心原生 API,是实现进度监控的基础,二者在功能、更新状态和进度监控支持上差异显著。

对比维度XMLHttpRequest(XHR)Fetch API
状态已停止更新(浏览器仍兼容,但无新功能)持续更新(W3C 标准,新增流式处理等功能)
功能覆盖基础异步请求能力,支持进度监控(上传 + 响应)覆盖 XHR 核心功能,支持 Promise、拦截器等,但不支持上传进度监控
第三方库依赖axios、jQuery.ajax(内部基于 XHR 封装)umijs request、fetch-interceptor(基于 Fetch 封装)
面试高频度极高(常问与 Fetch 的区别、进度监控实现)极高

2、响应进度监控实现(XHR + Fetch)

响应进度监控:指“服务器向客户端传输数据时”,实时计算已传输数据占总数据的比例(如加载大图片、Excel 文件时的进度条)。

2.1 XHR 实现响应进度监控(简单直观)

XHR 通过监听 progress 事件实现,事件对象会返回 loaded(已传输字节数)和 total(总字节数),直接用于计算进度。

代码示例:

js
// 1. 创建XHR实例
const xhr = new XMLHttpRequest();

// 2. 配置请求(GET请求,目标为大文件资源)
xhr.open('GET', '/api/download/large-file.xlsx', true);

// 3. 监听progress事件(核心:实时获取进度)
xhr.addEventListener('progress', (e) => {
  // 关键判断:确保total有值(部分响应可能不返回Content-Length)
  if (e.lengthComputable) {
    // 计算进度百分比(保留2位小数)
    const progressPercent = ((e.loaded / e.total) * 100).toFixed(2);
    // 更新UI(如进度条宽度、百分比文字)
    document.getElementById('progress-bar').style.width = `${progressPercent}%`;
    document.getElementById('progress-text').textContent = `${progressPercent}%`;
  }
});

// 4. 监听请求成功/失败
xhr.addEventListener('load', () => {
  if (xhr.status >= 200 && xhr.status < 300) {
    console.log('文件下载成功');
    // 处理响应数据(如解析Excel、生成下载链接)
  } else {
    console.error('请求失败,状态码:', xhr.status);
  }
});

xhr.addEventListener('error', () => {
  console.error('网络错误,请求中断');
});

// 5. 发送请求
xhr.send();

2.2 Fetch 实现响应进度监控(稍复杂,需手动处理)

Fetch 本身不直接暴露 progress 事件,需通过 HTTP 响应头的 Content-Length 字段获取总数据量,再通过响应体的 getReader() 方法逐块读取数据,累加已读取字节数计算进度。

核心逻辑
  1. 从响应头 Content-Length 中解析总数据量(注意:需服务器正确返回该字段);
  2. 通过 response.body.getReader() 获取 “读取器”,逐块读取响应数据;
  3. 每次读取后累加已读取字节数,与总数据量对比计算进度。

代码示例:

js
// 1. 发送Fetch请求
fetch('/api/download/large-file.xlsx')
  .then(async (response) => {
    // 2. 从响应头获取总数据量(需转为数字类型)
    const totalBytes = Number(response.headers.get('Content-Length'));
    if (!totalBytes) {
      throw new Error('服务器未返回Content-Length,无法计算进度');
    }

    // 3. 获取响应体读取器(stream流读取)
    const reader = response.body.getReader();
    // 4. 初始化已读取字节数
    let loadedBytes = 0;

    // 5. 循环读取数据块(直到读取完成)
    while (true) {
      // 读取一块数据(done为true表示读取完成)
      const { done, value } = await reader.read();
      if (done) break; // 读取完成,退出循环

      // 累加已读取字节数(value是Uint8Array,length即字节数)
      loadedBytes += value.length;

      // 6. 计算进度并更新UI
      const progressPercent = ((loadedBytes / totalBytes) * 100).toFixed(2);
      document.getElementById('progress-bar').style.width = `${progressPercent}%`;
      document.getElementById('progress-text').textContent = `${progressPercent}%`;
    }

    // 7. 读取完成,处理最终数据(如转为Blob用于下载)
    const blob = new Blob([await response.blob()], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
    const downloadUrl = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = downloadUrl;
    a.download = 'large-file.xlsx';
    a.click();
    URL.revokeObjectURL(downloadUrl); // 释放URL资源
  })
  .catch((error) => {
    console.error('请求失败:', error);
  });

3、上传进度监控实现(仅 XHR 支持)

上传进度监控: 指 “客户端向服务器传输数据时” 的进度(如文件上传、大表单提交),Fetch 目前不支持该功能,仅 XHR 可实现。

XHR 实现上传进度监控(核心:upload 属性)

XHR 的 upload 对象专门用于监听上传相关事件,通过 upload.onprogress 即可获取上传进度。

html
<!-- 页面结构:文件选择框 + 进度条 -->
<input type="file" id="file-input" accept=".jpg,.png">
<div class="progress-bar" id="upload-progress-bar" style="width: 0%; height: 8px; background: #42b983;"></div>
<div id="upload-progress-text">0%</div>
js
// 1. 获取DOM元素
const fileInput = document.getElementById('file-input');
const progressBar = document.getElementById('upload-progress-bar');
const progressText = document.getElementById('upload-progress-text');

// 2. 监听文件选择事件
fileInput.addEventListener('change', (e) => {
  const file = e.target.files[0]; // 获取选中的文件
  if (!file) return;

  // 3. 创建FormData(用于传输文件)
  const formData = new FormData();
  formData.append('uploadFile', file); // 'uploadFile'为后端接收的参数名

  // 4. 创建XHR实例并配置
  const xhr = new XMLHttpRequest();
  xhr.open('POST', '/api/upload/file', true);

  // 5. 监听上传进度(核心:upload.onprogress)
  xhr.upload.addEventListener('progress', (e) => {
    if (e.lengthComputable) {
      const progressPercent = ((e.loaded / e.total) * 100).toFixed(2);
      progressBar.style.width = `${progressPercent}%`;
      progressText.textContent = `${progressPercent}%`;
    }
  });

  // 6. 监听上传成功/失败
  xhr.addEventListener('load', () => {
    if (xhr.status >= 200 && xhr.status < 300) {
      const result = JSON.parse(xhr.responseText);
      console.log('上传成功,文件ID:', result.fileId);
      progressText.textContent = '上传完成!';
    } else {
      console.error('上传失败,状态码:', xhr.status);
      progressText.textContent = '上传失败';
    }
  });

  xhr.addEventListener('error', () => {
    console.error('网络错误,上传中断');
    progressText.textContent = '上传中断';
  });

  // 7. 发送请求(携带FormData)
  xhr.send(formData);
});

关键注意事项

  1. Content-Length 字段的必要性:无论是 XHR 还是 Fetch,计算进度都依赖服务器返回的 Content-Length 响应头(表示总数据量)。若服务器未返回该字段,e.lengthComputable 会为 false,无法计算真实进度。
  2. Fetch 的局限性:截至 2024 年 5 月,Fetch API 仍不支持上传进度监控,若业务需上传进度(如文件上传),需优先使用 XHR 或基于 XHR 封装的库(如 axios)。
  3. 兼容性:XHR 兼容所有现代浏览器及 IE(IE7+);Fetch 在 IE 中完全不支持,若需兼容 IE,需使用 whatwg-fetch 等 polyfill。

The future is promising