You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在Ajax POST请求中获取进度百分比?解决批量计分卡生成耗时问题

如何在Ajax POST请求中获取服务器端任务的进度百分比?

嘿,我完全懂你的烦恼——让用户盯着加载条干等7分钟,体验实在太差了!你之前尝试的代码没起效,其实是因为XHR的onprogressupload.onprogress只负责监听「数据传输阶段」的进度,比如上传文件的字节数、下载响应的字节数,但你的场景是服务器在后台耗时处理大量数据生成计分卡,这个处理过程的进度,XHR本身是没法自动感知的。

核心问题分析

你之前的代码逻辑是对的,但用错了场景:

xhr.onprogress:监听服务器返回响应的下载进度(比如服务器给你发一个大文件时)
xhr.upload.onprogress:监听你发送请求的上传进度(比如你上传一个大表单/文件时)

而你的情况是:请求已经发送完成(数据传输结束),服务器在后台处理任务,这时候XHR处于等待响应的状态,上述两个事件都不会触发——因为没有数据在传输,自然没有进度可以监听。

解决方案:让服务器主动推送进度

要获取服务器端任务的处理进度,需要服务器和前端配合,把进度数据主动传递给前端,常见的实现方式有两种:轮询(简单易实现)和实时推送(SSE/WebSocket,体验更好)。


方案1:轮询(最容易落地)

思路是:

  1. 前端发送POST请求启动生成任务,服务器返回一个唯一的「任务ID」
  2. 前端用这个任务ID,每隔一段时间发送GET请求到进度接口,获取当前完成数/总数
  3. 根据返回的进度数据更新页面,直到任务完成
前端代码示例
// 第一步:启动计分卡生成任务
$.ajax({
  url: '/api/generate-scorecards',
  method: 'POST',
  data: { totalCount: 1000 }, // 告诉服务器要生成的总条数
  success: function(response) {
    const taskId = response.taskId; // 服务器返回的唯一任务ID
    // 第二步:开始轮询进度
    startProgressPolling(taskId);
  },
  error: function() {
    alert('任务启动失败,请重试');
  }
});

// 轮询进度的函数
function startProgressPolling(taskId) {
  // 每秒查询一次进度
  const pollInterval = setInterval(() => {
    $.ajax({
      url: `/api/scorecard-progress/${taskId}`,
      method: 'GET',
      success: function(progressData) {
        const { completed, total } = progressData;
        const percentage = Math.round((completed / total) * 100);
        
        // 更新页面上的进度条(假设你有一个id为progress-bar的元素)
        $('#progress-bar').css('width', `${percentage}%`).text(`${percentage}%`);
        console.log(`当前进度:${percentage}%`);
        
        // 如果任务完成,停止轮询
        if (completed === total) {
          clearInterval(pollInterval);
          alert('计分卡生成完成!');
          // 这里可以跳转结果页/触发下载等操作
        }
      },
      error: function() {
        clearInterval(pollInterval);
        alert('获取进度失败,请检查任务状态');
      }
    });
  }, 1000);
}
服务器端需要做的事
  • 收到生成请求时,生成一个唯一taskId,将任务放入异步队列(比如用Redis、Celery,或者自己实现的任务池),同时初始化进度数据(completed: 0, total: 1000)存储到缓存/数据库
  • 后台任务处理时,每生成一批计分卡(比如每10条),就更新对应taskIdcompleted数值
  • 提供/api/scorecard-progress/{taskId}接口,根据taskId从缓存/数据库中读取并返回当前进度数据

方案2:实时推送(SSE,体验更流畅)

轮询会有1秒左右的延迟,而且频繁请求会增加服务器压力,SSE(Server-Sent Events)是更适合的实时方案——它允许服务器单向推送数据到前端,无需前端频繁请求。

前端代码示例
// 启动生成任务
$.ajax({
  url: '/api/generate-scorecards',
  method: 'POST',
  data: { totalCount: 1000 },
  success: function(response) {
    const taskId = response.taskId;
    // 建立SSE连接
    const eventSource = new EventSource(`/api/scorecard-sse/${taskId}`);
    
    // 监听服务器发送的进度事件
    eventSource.onmessage = function(event) {
      const progressData = JSON.parse(event.data);
      const percentage = Math.round((progressData.completed / progressData.total) * 100);
      
      $('#progress-bar').css('width', `${percentage}%`).text(`${percentage}%`);
      console.log(`当前进度:${percentage}%`);
      
      // 任务完成后关闭连接
      if (progressData.completed === progressData.total) {
        eventSource.close();
        alert('计分卡生成完成!');
      }
    };
    
    // 处理连接错误
    eventSource.onerror = function() {
      eventSource.close();
      alert('实时进度连接中断,将切换为轮询方式');
      startProgressPolling(taskId); // 降级为轮询
    };
  }
});
服务器端SSE示例(以Node.js为例)
app.get('/api/scorecard-sse/:taskId', async (req, res) => {
  // 设置SSE响应头
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');
  
  const taskId = req.params.taskId;
  
  // 每秒推送一次进度
  const pushInterval = setInterval(async () => {
    const progressData = await getProgressFromCache(taskId); // 从缓存获取进度
    // 以SSE格式发送数据
    res.write(`data: ${JSON.stringify(progressData)}\n\n`);
    
    // 任务完成后停止推送并关闭连接
    if (progressData.completed === progressData.total) {
      clearInterval(pushInterval);
      res.end();
    }
  }, 1000);
  
  // 客户端断开连接时清理定时器
  req.on('close', () => {
    clearInterval(pushInterval);
    res.end();
  });
});

总结

你的场景核心是服务器端异步任务的进度感知,XHR的默认进度事件帮不上忙,必须让服务器主动把进度数据传递给前端。轮询是最容易实现的方案,适合快速落地;SSE则能提供更流畅的实时体验,适合对用户体验要求较高的场景。

内容的提问来源于stack exchange,提问作者Son Rhey Deiparine

火山引擎 最新活动