如何无需等待完整结果,逐段读取流式响应?
解决方案
要实现实时接收ChatGPT的流式响应,需要同时调整后端响应头和前端的处理逻辑,具体步骤如下:
一、后端调整
当前后端设置的application/octet-stream是用于文件下载的类型,不适合流式推送,需要修改响应头适配流式传输格式:
const chatStream = await fetch("https://api.openai.com/v1/chat/completions", { method: "POST", body: JSON.stringify({ model: process.env.CHAT_GPT_MODEL, messages, n: 1, max_tokens: parseInt(process.env.CHAT_GPT_TOKEN_LIMIT ?? "2000"), stream: true, }), headers: { "Content-Type": "application/json", Authorization: "Bearer " + process.env.CHAT_GPT_API_KEY, }, }); // 修改为流式响应头 res.setHeader("Content-Type", "text/event-stream"); res.setHeader("Cache-Control", "no-cache"); // 禁止缓存响应片段 res.setHeader("Connection", "keep-alive"); // 保持长连接 // 继续管道转发响应 chatStream.body.pipe(res);
同时去掉原本的Content-Disposition头,避免浏览器触发自动下载行为。
二、前端调整
方式1:使用原生Fetch API(推荐)
Angular的HttpClient默认会等待完整响应才返回结果,原生Fetch支持直接读取ReadableStream,能精准处理每个返回的响应片段:
async function handleStreamResponse() { const url = this.getUrl('/create-new-chat-gpt-room'); const token = this.auth?.loginToken; const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ title }) }); if (!response.body) { throw new Error('响应流为空'); } const reader = response.body.getReader(); const decoder = new TextDecoder('utf-8'); while (true) { const { done, value } = await reader.read(); if (done) break; // 把二进制片段解码为文本 const chunk = decoder.decode(value); // 处理ChatGPT的流式格式:每行以data:开头,最终以[DONE]标记结束 const lines = chunk.split('\n').filter(line => line.trim() && line.startsWith('data:')); for (const line of lines) { const data = line.slice(5).trim(); if (data === '[DONE]') continue; try { const json = JSON.parse(data); const content = json.choices[0].delta.content; if (content) { console.log(content); // 实时打印每一段返回内容 } } catch (err) { console.error('解析响应失败:', err); } } } } // 调用处理函数 handleStreamResponse().catch(err => console.error('请求出错:', err));
方式2:使用Angular HttpClient
如果坚持用Angular的HttpClient,需要开启进度报告并监听下载事件:
import { HttpEventType } from '@angular/common/http'; // ... this.http.post(this.getUrl('/create-new-chat-gpt-room'), { title }, { headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${this.auth?.loginToken}` }, responseType: 'text', observe: 'events', reportProgress: true // 开启进度报告,获取分段响应 }).pipe( catchError(this.catchError.bind(this)) ).subscribe(event => { // 监听下载进度事件,获取部分响应文本 if (event.type === HttpEventType.DownloadProgress && event.partialText) { const chunk = event.partialText; const lines = chunk.split('\n').filter(line => line.trim() && line.startsWith('data:')); for (const line of lines) { const data = line.slice(5).trim(); if (data === '[DONE]') continue; try { const json = JSON.parse(data); const content = json.choices[0].delta.content; if (content) { console.log(content); } } catch (err) { console.error('解析失败:', err); } } } else if (event.type === HttpEventType.Response) { // 响应全部完成后的收尾处理 } });
核心原因
你之前使用Blob和FileReader的方式,会等待整个响应完全下载到本地Blob后才触发onload事件,自然无法实时获取分段内容。改用流式读取后,能在每个响应片段到达时立即处理,实现实时打印的需求。
内容的提问来源于stack exchange,提问作者Alvin Stefanus
相关产品推荐
相关产品推荐

