湖州市文章资讯

AJAX请求上传下载进度监控实现方式

2026-03-26 14:55:02 浏览次数:2
详细信息

一、XMLHttpRequest(传统方式)

1. 上传进度监控

function uploadWithProgress(file) {
  const xhr = new XMLHttpRequest();
  const formData = new FormData();
  formData.append('file', file);

  // 监听上传进度事件
  xhr.upload.addEventListener('progress', function(event) {
    if (event.lengthComputable) {
      const percentComplete = Math.round((event.loaded * 100) / event.total);
      console.log(`上传进度: ${percentComplete}%`);

      // 更新UI进度条
      updateProgressBar('upload', percentComplete);
    }
  });

  // 监听完成事件
  xhr.upload.addEventListener('load', function() {
    console.log('上传完成');
  });

  // 监听错误事件
  xhr.upload.addEventListener('error', function() {
    console.error('上传失败');
  });

  xhr.open('POST', '/upload');
  xhr.send(formData);
}

2. 下载进度监控

function downloadWithProgress(url) {
  const xhr = new XMLHttpRequest();
  xhr.responseType = 'blob'; // 或其他类型

  // 监听下载进度事件
  xhr.addEventListener('progress', function(event) {
    if (event.lengthComputable) {
      const percentComplete = Math.round((event.loaded * 100) / event.total);
      console.log(`下载进度: ${percentComplete}%`);

      // 更新UI进度条
      updateProgressBar('download', percentComplete);
    }
  });

  xhr.onload = function() {
    if (xhr.status === 200) {
      const blob = xhr.response;
      const url = window.URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = url;
      a.download = 'file.zip';
      a.click();
      window.URL.revokeObjectURL(url);
    }
  };

  xhr.open('GET', url);
  xhr.send();
}

二、Fetch API + Streams API(现代方式)

1. 下载进度监控(Fetch)

async function fetchWithProgress(url) {
  const response = await fetch(url);

  if (!response.ok) {
    throw new Error('Network response was not ok');
  }

  const contentLength = response.headers.get('content-length');
  const total = parseInt(contentLength, 10);
  let loaded = 0;

  const reader = response.body.getReader();
  const chunks = [];

  while (true) {
    const { done, value } = await reader.read();

    if (done) break;

    chunks.push(value);
    loaded += value.length;

    // 计算进度
    if (total) {
      const percent = Math.round((loaded / total) * 100);
      console.log(`下载进度: ${percent}%`);
      updateProgressBar('download', percent);
    }
  }

  // 合并所有chunks
  const blob = new Blob(chunks);
  return blob;
}

2. 上传进度监控(Fetch + XMLHttpRequest)

由于Fetch API原生不支持上传进度监控,通常需要结合其他方式:

// 方案1:使用fetch但监听XMLHttpRequest进度
function uploadWithFetchProgress(file, url) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    const formData = new FormData();
    formData.append('file', file);

    xhr.upload.onprogress = function(event) {
      if (event.lengthComputable) {
        const percent = Math.round((event.loaded * event.total) * 100);
        updateProgressBar('upload', percent);
      }
    };

    xhr.onload = function() {
      if (xhr.status >= 200 && xhr.status < 300) {
        resolve(xhr.response);
      } else {
        reject(new Error(xhr.statusText));
      }
    };

    xhr.onerror = function() {
      reject(new Error('Network error'));
    };

    xhr.open('POST', url);
    xhr.send(formData);
  });
}

三、Axios库的实现

Axios提供了内置的进度监控支持:

// 上传进度监控
axios.post('/upload', formData, {
  headers: {
    'Content-Type': 'multipart/form-data'
  },
  onUploadProgress: function(progressEvent) {
    const percentCompleted = Math.round(
      (progressEvent.loaded * 100) / progressEvent.total
    );
    console.log(`上传进度: ${percentCompleted}%`);
  }
});

// 下载进度监控
axios.get('/download/file.zip', {
  responseType: 'blob',
  onDownloadProgress: function(progressEvent) {
    const percentCompleted = Math.round(
      (progressEvent.loaded * 100) / progressEvent.total
    );
    console.log(`下载进度: ${percentCompleted}%`);
  }
})
.then(response => {
  const url = window.URL.createObjectURL(new Blob([response.data]));
  const link = document.createElement('a');
  link.href = url;
  link.setAttribute('download', 'file.zip');
  document.body.appendChild(link);
  link.click();
});

四、完整的进度监控组件示例

class ProgressMonitor {
  constructor(options = {}) {
    this.options = {
      updateInterval: 100, // 更新频率(ms)
      ...options
    };
    this.progressBars = new Map();
  }

  // 创建进度条UI
  createProgressBar(id, label) {
    const container = document.createElement('div');
    container.className = 'progress-container';
    container.id = `progress-${id}`;

    const labelEl = document.createElement('div');
    labelEl.textContent = label;

    const progressBar = document.createElement('div');
    progressBar.className = 'progress-bar';

    const progressFill = document.createElement('div');
    progressFill.className = 'progress-fill';
    progressFill.style.width = '0%';

    const percentage = document.createElement('div');
    percentage.className = 'progress-percentage';
    percentage.textContent = '0%';

    progressBar.appendChild(progressFill);
    container.appendChild(labelEl);
    container.appendChild(progressBar);
    container.appendChild(percentage);

    this.progressBars.set(id, {
      fill: progressFill,
      percentage: percentage,
      container: container
    });

    return container;
  }

  // 更新进度
  updateProgress(id, percent) {
    const bar = this.progressBars.get(id);
    if (bar) {
      bar.fill.style.width = `${percent}%`;
      bar.percentage.textContent = `${percent}%`;
    }
  }

  // 移除进度条
  removeProgressBar(id) {
    const bar = this.progressBars.get(id);
    if (bar) {
      bar.container.remove();
      this.progressBars.delete(id);
    }
  }
}

// 使用示例
const monitor = new ProgressMonitor();

// 上传文件
async function uploadFile(file) {
  const progressId = `upload-${Date.now()}`;
  const progressEl = monitor.createProgressBar(progressId, '上传文件');
  document.body.appendChild(progressEl);

  const formData = new FormData();
  formData.append('file', file);

  try {
    const response = await axios.post('/api/upload', formData, {
      headers: { 'Content-Type': 'multipart/form-data' },
      onUploadProgress: (progressEvent) => {
        const percent = Math.round(
          (progressEvent.loaded * 100) / progressEvent.total
        );
        monitor.updateProgress(progressId, percent);
      }
    });

    // 上传完成,延迟移除进度条
    setTimeout(() => monitor.removeProgressBar(progressId), 1000);
    return response.data;
  } catch (error) {
    monitor.updateProgress(progressId, 0);
    console.error('上传失败:', error);
    throw error;
  }
}

五、注意事项

跨域问题:需要服务器设置正确的CORS头(Access-Control-Allow-Origin等)

内容长度:服务器需要正确返回Content-Length头信息,进度计算才准确

大文件处理:对于超大文件,考虑分片上传/断点续传

性能优化:避免过于频繁的UI更新,使用节流(throttle)

错误处理:确保网络错误、服务器错误等异常情况有相应处理

六、服务器端配合

// Node.js + Express 示例
const express = require('express');
const multer = require('multer');
const app = express();

// 设置上传中间件
const upload = multer({ dest: 'uploads/' });

app.post('/upload', upload.single('file'), (req, res) => {
  res.json({
    success: true,
    filename: req.file.filename,
    size: req.file.size
  });
});

// 支持Range请求(用于断点续传)
app.get('/download/:filename', (req, res) => {
  const filename = req.params.filename;
  const filePath = `uploads/${filename}`;
  const stat = fs.statSync(filePath);
  const fileSize = stat.size;

  // 设置正确的响应头
  res.setHeader('Content-Length', fileSize);
  res.setHeader('Content-Type', 'application/octet-stream');
  res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);

  // 支持Range请求
  const range = req.headers.range;
  if (range) {
    const parts = range.replace(/bytes=/, "").split("-");
    const start = parseInt(parts[0], 10);
    const end = parts[1] ? parseInt(parts[1], 10) : fileSize - 1;
    const chunksize = (end - start) + 1;

    res.writeHead(206, {
      'Content-Range': `bytes ${start}-${end}/${fileSize}`,
      'Accept-Ranges': 'bytes',
      'Content-Length': chunksize,
    });

    const fileStream = fs.createReadStream(filePath, { start, end });
    fileStream.pipe(res);
  } else {
    res.writeHead(200);
    fs.createReadStream(filePath).pipe(res);
  }
});

选择哪种实现方式取决于你的项目需求、浏览器兼容性要求和使用的技术栈。现代项目推荐使用Fetch API或Axios,传统项目可以使用XMLHttpRequest。

相关推荐