You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用fetch处理text/event-stream时,reader未实时返回chunk问题咨询

流式SSE响应处理异常:Reader未逐Chunk返回数据

我使用fetch、ReadableStream Reader和TextDecoder处理Content-Type: text/event-stream的流式响应,预期Reader会在每个Chunk到达时立即返回,但实际所有Chunk接收完成后才一次性返回,不符合预期。控制台中“sselog::: while”仅输出一次,但后端实际发送了major和message两类消息。请问我的流式响应处理方式是否有误?

代码示例

const sseController = new AbortController();
fetch(url, {
    method: 'POST',
    mode: 'cors',
    credentials: 'include',
    headers: {
        Accept: 'text/event-stream,application/json',
        'Content-Type': 'application/json',
    },
    body: JSON.stringify({
        ...params,
    }),
    signal: sseController.signal,
}).then(async (response) => {
    console.log('sselog::: open', new Date());
    if (!response.ok || !response.body) {
        // TODO onerror
        return;
    }

    const reader = response.body.getReader();
    const decoder = new TextDecoder('utf-8');

    while (true) {
        const { done, value } = await reader?.read();
        console.log('sselog::: while', value);
        const str = new TextDecoder().decode(value);
        // TODO onmessage
        if (done) {
            return;
        }
    }
});

问题分析与修复方案

你的代码存在两个核心问题,同时后端配置也可能影响流式响应的触发:

1. TextDecoder使用错误

  • 循环内重复创建TextDecoder实例,而非复用初始化时的decoder,导致丢失流式解码的缓存状态。
  • 未启用stream: true选项,该选项会让TextDecoder缓存不完整的UTF-8序列,直到后续Chunk补充完整再解码,是流式处理的关键配置。

2. 请求头设置冗余

Accept头同时包含text/event-stream和application/json,可能导致服务器优先返回JSON而非流式SSE响应,建议只保留text/event-stream。

3. 后端配置检查(必做)

SSE生效依赖后端严格遵循规范:

  • 响应头必须设置Content-Type: text/event-stream,优先级高于请求头的Accept字段
  • 必须添加Cache-Control: no-cache防止浏览器缓存响应
  • 每个消息块必须以\n\n作为结束标记
  • 禁止对SSE响应启用Gzip等压缩(压缩会合并所有Chunk后发送)

修正后的代码

const sseController = new AbortController();
fetch(url, {
    method: 'POST',
    mode: 'cors',
    credentials: 'include',
    headers: {
        Accept: 'text/event-stream', // 仅保留SSE类型
        'Content-Type': 'application/json',
    },
    body: JSON.stringify({
        ...params,
    }),
    signal: sseController.signal,
}).then(async (response) => {
    console.log('sselog::: open', new Date());
    if (!response.ok || !response.body) {
        // TODO 错误处理
        return;
    }

    const reader = response.body.getReader();
    // 启用流式解码,保留不完整序列缓存
    const decoder = new TextDecoder('utf-8', { stream: true });

    while (true) {
        const { done, value } = await reader.read();
        if (done) {
            console.log('sselog::: stream completed');
            return;
        }
        console.log('sselog::: received chunk', value);
        // 复用同一个decoder解码
        const decodedStr = decoder.decode(value);
        // 拆分SSE消息(按标准的\n\n分隔符)
        const messages = decodedStr.split(/\n\n/).filter(Boolean);
        messages.forEach(message => {
            console.log('sselog::: parsed message', message);
            // TODO 处理单个SSE消息,例如提取data字段
            // const data = message.split('data: ')[1];
            // const parsedData = JSON.parse(data);
        });
    }
});

内容的提问来源于stack exchange,提问作者smilebuz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 10:35:13