Skip to content
 

解决前端流式输出被浏览器截断的问题

更新: 9/11/2025字数: 0 字 时长: 0 分钟

一、问题背景

在开发 ai 对话项目中,使用 SSE 流式传输技术。会遇到流式数据在传输过程中被浏览器不规则地截断的问题,导致数据解析(JSON.parse())失败错误或内容不完整。

二、问题分析

当使用 Fetch API 处理流式响应时,数据是以分块(chunk)形式到达的。后端返回的数据是完整的,network中是正常的,但是浏览器在解析数据时,可能会因为某些原因(如网络问题、浏览器限制)导致数据被截断。

控制台打印日志发现完整的字符串对象只打印出来一半,导致解析失败,程序报错终止。

三、解决方案:Buffer 缓存机制

最有效的解决方案是实现一个前端 Buffer 缓存系统,将到达的数据块先缓存起来,然后按照消息边界进行完整提取和处理。

提示

创建缓冲区来累积数据块,然后根据特定分隔符(如换行符\n)从缓冲区中提取完整消息,保留下不完整的部分等待后续数据。

3.1 前端主要代码实现

javascript

    try {
      const response = await chatCompletions(chatRequest);
      if (!response.ok) {
        throw new Error(`会话接口异常`);
      }

      const reader = response.body?.getReader();
      if (!reader) {
        throw new Error(`无法获取流式数据`);
      }

      const decoder = new TextDecoder("utf-8");
      let buffer = ""; 
      while (true) {
        const { done, value } = await reader.read();
        if (done) break;

        // 解码数据并添加到缓冲区
        buffer += decoder.decode(value, { stream: true }); 

        // 处理缓冲区中的完整数据行
        let lineEndIndex; 
        while ((lineEndIndex = buffer.indexOf("\n")) !== -1) {
          const line = buffer.slice(0, lineEndIndex).trim(); 
          buffer = buffer.slice(lineEndIndex + 1); 

          if (line.startsWith("data: ")) {
            const dataStr = line.slice(6); // 移除 "data: " 前缀
            if (dataStr === "[DONE]") {
              setChatHistory((prevHistory) => {
                prevHistory[aiMessageIndex].loading = false; // 流结束 loading 设置为false
                return prevHistory;
              });
              setIsStreaming(false);
              return fullContent;
            }

            try {
              const data = JSON.parse(dataStr);

              // 处理数据块
              if (data.delta?.content) {
                fullContent += data.delta.content;
                setChatHistory((prevHistory) => {
                  prevHistory[aiMessageIndex].content = fullContent;
                  return prevHistory;
                });
              }

              if (data.finish_reason === "stop") {
                console.log("完成原因:", data);
                // 储存人才数据的id
                if (data.message_id) {
                  talentDataMessageId.current = data.message_id;
                }
              }
            } catch (e) {
              alert("解析JSON失败" + e);
              console.error("解析JSON失败:", e, "原始数据:", dataStr);
              return false;
            }
          }
        }
      }
    }
    ```

我见青山多妩媚,料青山见我应如是。