如何用Nodejs的Express向前端分块发送响应实现进度实时推送
实现思路
你这个场景用HTTP分块流式响应即可满足单API请求内持续推送进度的需求,不需要额外建立长连接,逻辑轻量且完全适配你现有的技术栈:
后端(Express)实现步骤
- 首先配置响应头,开启分块传输、禁用缓存:
const express = require('express'); const app = express(); const progress = require('progress-stream'); app.post('/api/process-file', async (req, res) => { // 核心响应头配置 res.setHeader('Content-Type', 'text/plain; charset=utf-8'); res.setHeader('Transfer-Encoding', 'chunked'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); // 跨域场景下添加该配置,允许前端读取流式内容 res.setHeader('Access-Control-Expose-Headers', '*');
- 分步执行业务逻辑,每步进度更新时往响应流推送JSON格式的进度事件,每次推送后调用
res.flush()确保内容实时发出:
try { // 步骤1:上传S3,结合progress-stream获取上传进度 const fileStream = fs.createReadStream(req.file.path); const progressStream = progress({ length: req.file.size, time: 100 // 每100ms返回一次进度 }); progressStream.on('progress', (p) => { // 推送步骤1实时进度 res.write(JSON.stringify({ step: 1, status: 'processing', percent: Math.round(p.percentage), msg: '文件上传中' }) + '\n'); // 加换行符避免多个事件粘包 res.flush?.(); }); // 将progressStream传入你已有的S3上传方法即可 await uploadToS3(progressStream, req.file.originalname); // 步骤1完成推送 res.write(JSON.stringify({ step: 1, status: 'completed', percent: 100, msg: '文件上传完成' }) + '\n'); res.flush?.(); // 步骤2:文件编码处理 res.write(JSON.stringify({ step: 2, status: 'processing', percent: 0, msg: '文件编码中' }) + '\n'); res.flush?.(); await processFileEncoding(req.file.originalname); res.write(JSON.stringify({ step: 2, status: 'completed', percent: 100, msg: '文件编码完成' }) + '\n'); res.flush?.(); // 步骤3:更新数据库 res.write(JSON.stringify({ step: 3, status: 'processing', percent: 0, msg: '数据更新中' }) + '\n'); res.flush?.(); await updateDatabaseRecord(req.file.originalname); // 全流程完成推送 res.write(JSON.stringify({ step: 3, status: 'completed', percent: 100, msg: '全部任务完成' }) + '\n'); res.flush?.(); // 所有步骤完成后结束响应 res.end(); } catch (err) { // 异常处理,推送错误后结束响应 res.write(JSON.stringify({ status: 'error', msg: err.message }) + '\n'); res.end(); } })
前端(Vue+Axios)实现步骤
- 配置Axios开启流式响应,监听分块内容实时更新UI:
// 发起请求方法 async function uploadAndProcess(file) { const formData = new FormData(); formData.append('file', file); await axios.post('/api/process-file', formData, { // 开启流式响应 responseType: 'stream', // 监听下载进度事件,接收服务端推送的分块内容 onDownloadProgress: (progressEvent) => { // 解码收到的内容,按换行拆分事件 const chunks = progressEvent.event.target.responseText.split('\n'); // 取最新的有效事件 const latestEvent = chunks.filter(Boolean).pop(); if (latestEvent) { const progressData = JSON.parse(latestEvent); // 更新UI逻辑 updateProgressUI(progressData); } } }); } // UI更新逻辑,完全匹配你的交互需求 function updateProgressUI(data) { if (data.status === 'error') { alert(`处理失败:${data.msg}`); return; } switch(data.step) { case 1: // 步骤1更新进度条 this.uploadPercent = data.percent; break; case 2: // 步骤2展示自定义加载提示 this.loadingTip = data.msg; break; case 3: if (data.status === 'completed') { // 步骤3完成弹出成功弹窗 this.$modal.success('任务处理完成'); } break; } }
注意事项
- 如果你用了Express的
compression压缩中间件,必须在每次res.write后调用res.flush(),否则内容会被缓存无法实时推送到前端 - 流式响应的每个事件用换行符分隔,避免前端收到多个事件粘包无法解析
- 步骤执行过程中如果出现异常,一定要推送错误事件后调用
res.end()结束响应,避免前端请求一直挂起
内容的提问来源于stack exchange,提问作者Anirudh
相关产品推荐
相关产品推荐

