如何基于Express.js与Node.js实现服务端多段JSON实时增量推送
问题根源
- 当前实现达不到预期,本质是两个机制错位导致的:
- 客户端用的
response.json()是Fetch API内置的便捷方法,设计目标是等整个响应流完全接收完毕后,一次性把全量内容解析为JSON对象,天生不支持流式传输过程中的分块数据处理,自然不会在中途触发回调。 - 服务端连续写入的多段独立JSON没有任何分隔标记,客户端收到的是两段JSON直接拼接的连续字节,既无法切割出独立可解析的单元,拼接后的内容也不符合JSON语法规范,最终解析必然报错。
- 客户端用的
实现方案(NDJSON流式传输)
你期望的效果完全可以实现,不需要引入WebSocket、SSE这类额外的推送技术,基于现有HTTP长连接,采用换行分隔JSON(NDJSON) 格式就能落地——核心约定是每一段独立序列化的JSON内容末尾追加一个\n换行符作为分隔标记,客户端收到字节流后按换行符切割出独立的JSON单元,逐个解析处理。
服务端配置
- 设置适配流式传输的响应头,明确告诉代理和浏览器这是流式内容,不要等全量接收完再处理:
response.setHeader('Content-Type', 'application/x-ndjson'); response.setHeader('X-Content-Type-Options', 'nosniff'); response.setHeader('Cache-Control', 'no-cache'); // 立刻把响应头发送给客户端,开启长连接 response.flushHeaders();
- 每次写入更新内容时,在序列化的JSON字符串末尾追加换行符作为分隔:
// 第一次状态更新 response.write(JSON.stringify({ hello: ["world"], foo: "bar" }) + '\n'); // 执行耗时的计算逻辑... // 第二次状态更新 response.write(JSON.stringify({ hello: ["world", "anotherWorld"], foo: "cat" }) + '\n'); // 所有计算完成后终止连接 response.end();
注意:如果使用了Express的gzip压缩中间件,需要确保中间件支持逐块flush,否则缓冲机制会攒够一批数据才统一发送,会导致客户端收到数据的时机被延迟,无法实现写入即推送的效果。
客户端逻辑改造
放弃response.json()的一次性解析方法,直接读取响应的原始字节流,逐块累积缓存、切割处理:
const handleUpdate = (jsonStr) => { // 解析单段JSON,触发对应回调 const updateContent = JSON.parse(jsonStr); onReturn(updateContent); }; fetch(url).then(async (response) => { const streamReader = response.body.getReader(); const textDecoder = new TextDecoder('utf-8'); let cacheBuffer = ''; while (true) { const { done, value } = await streamReader.read(); // 流传输结束,处理缓存中剩余的最后一段内容 if (done) { if (cacheBuffer.trim()) handleUpdate(cacheBuffer); break; } // 把当前收到的二进制字节转成文本,拼接到缓存中 cacheBuffer += textDecoder.decode(value, { stream: true }); // 循环切割出缓存里所有以换行符分隔的完整JSON片段 let lineBreakPos; while ((lineBreakPos = cacheBuffer.indexOf('\n')) !== -1) { const singleLine = cacheBuffer.slice(0, lineBreakPos).trim(); if (singleLine) handleUpdate(singleLine); // 把已经处理完的内容从缓存中移除,避免缓存无限增长 cacheBuffer = cacheBuffer.slice(lineBreakPos + 1); } } });
按照这套实现,服务端每写入一段带换行的JSON数据,客户端收到对应字节后就会立刻切出完整的可解析JSON对象触发回调,处理完成后自动清理已处理的缓存内容,完全匹配提出的需求。
避坑提醒
- 不要把
Content-Type设置为application/json,浏览器和中间代理会针对这个类型做全量缓冲,哪怕你逐块调用response.write,数据也会被攒到响应结束才统一发送给客户端。 - 序列化JSON的时候不要手动在字符串内容里插入换行符,
JSON.stringify会自动对字符串内的特殊换行做转义,只要使用标准序列化逻辑就不会打乱客户端的切割规则。
内容的提问来源于stack exchange,提问作者memo
相关产品推荐
相关产品推荐

