Axios与Express:如何通过单请求实现后端分步骤返回处理响应
完全可以实现,单请求流式传输是最匹配你需求的方案
你之前担心的多次请求重复走公共校验逻辑的问题,完全可以靠HTTP分块流式响应彻底解决,全程只需要向后端发1次请求,后端每完成一个处理节点就实时把进度推给前端,前端收到一段就更新一段通知,不需要等全部处理完成才拿到结果。
后端Node.js 侧实现
Node 生态下不管是原生http模块、Express、Koa还是NestJS,都原生支持分块传输的响应能力,不需要引入额外的消息队列、WebSocket这类重型组件:
- 首先配置响应头,关闭各级缓冲,确保数据写完立刻推送到前端:
- 把响应类型设为
application/x-ndjson(换行分隔的JSON格式,方便前后端逐行解析,比纯文本流更容易处理结构化的进度消息) - 设置
Transfer-Encoding: chunked,不要设置固定长度的Content-Length,避免浏览器等待全量内容才触发接收 - 如果场景走Nginx反向代理,记得加
X-Accel-Buffering: no头,关闭Nginx的响应缓冲
- 把响应类型设为
- 业务处理流到哪个节点,就直接往响应对象里写对应的进度消息,等全流程结束再调用
end收尾。
举个Express的极简可运行示例:
// 后端示例代码 app.get('/api/process-with-progress', async (req, res) => { // 配置流式响应头 res.setHeader('Content-Type', 'application/x-ndjson'); res.setHeader('Transfer-Encoding', 'chunked'); res.setHeader('X-Accel-Buffering', 'no'); // 节点1:公共校验逻辑(只做一次) const authCheck = await validateRequestToken(req); if (!authCheck.pass) { res.write(JSON.stringify({ step: 1, message: '身份校验失败,请重新登录' }) + '\n'); return res.end(); } res.write(JSON.stringify({ step: 1, message: '身份校验、参数校验完成' }) + '\n'); // 节点2:核心业务逻辑处理 res.write(JSON.stringify({ step: 2, message: '开始读取业务数据,预计耗时3s' }) + '\n'); const businessData = await fetchBusinessData(req.query.id); // 节点3:结果加工 res.write(JSON.stringify({ step: 3, message: '数据读取完成,正在加工最终结果' }) + '\n'); const finalResult = await processData(businessData); // 节点4:返回最终结果,收尾 res.write(JSON.stringify({ step: 4, message: '全流程处理完成', data: finalResult }) + '\n'); res.end(); });
前端React + Axios 侧实时接收逻辑
很多人误以为Axios只能接收全部响应内容后才返回结果,实际上它内置的下载进度回调完全可以拿到传输过程中的分块内容,不需要换用Fetch API或者其他库:
- 发起请求时把
responseType设为stream,同时监听onDownloadProgress进度事件 - 用一个变量缓存已经解析过的内容,每次拿到新的累计传输内容时,截取出新增的未解析片段,按换行符拆成单条JSON,逐行更新到页面的通知列表里
组件内的实现示例:
// React组件内的请求逻辑示例 import axios from 'axios'; import { useState } from 'react'; export default function ProgressNotify() { const [notifyList, setNotifyList] = useState([]); const triggerProcess = async () => { setNotifyList([]); let parsedContent = ''; // 缓存已经解析过的内容,避免重复处理 await axios.get('/api/process-with-progress', { responseType: 'stream', // 监听下载进度,实时拿到分块内容 onDownloadProgress: (progressEvent) => { const fullReceived = progressEvent.event.target.response; // 提取本次进度更新里新增的未解析内容 const newFragment = fullReceived.replace(parsedContent, ''); parsedContent = fullReceived; // 逐行解析NDJSON格式的消息,更新通知 newFragment.split('\n').forEach(line => { const validLine = line.trim(); if (!validLine) return; try { const notify = JSON.parse(validLine); setNotifyList(prev => [...prev, notify.message]); } catch (err) { // 忽略传输过程中没接收完整的截断行 } }); } }); }; return ( <div> <button onClick={triggerProcess}>启动任务</button> <div className="notify-container"> {notifyList.map((msg, index) => <p key={index}>{msg}</p>)} </div> </div> ); }
方案适配说明
- 相比你之前考虑的多请求逐次等待方案,这个流式方案全程只做一次身份校验、一次参数解析,完全避免了重复公共逻辑带来的性能损耗,网络往返次数从N次降到1次,整体性能开销最低
- 如果后续需要实现服务端主动推送通知(不需要前端主动发请求),可以在这套逻辑基础上平滑迁移到SSE服务端推送,实现逻辑和当前的流方案高度重合,不需要做大幅改造
内容的提问来源于stack exchange,提问作者ImanGM
相关产品推荐
相关产品推荐

