共计 2683 个字符,预计需要花费 7 分钟才能阅读完成。
ChatGPT 流式响应特点与技术难点
ChatGPT 的流式响应具有两个核心特征:实时分段传输和逐字渲染效果。技术实现上需要解决以下问题:

- 网络层:需要保持长连接持续接收数据分片
- 渲染层:需平衡性能与实时性,避免频繁 DOM 操作导致页面卡顿
- 交互层:要处理用户中途打断、网络异常等边界情况
WebSocket vs Server-Sent Events 技术选型
WebSocket 方案
- 优点:
- 全双工通信,可双向交互
- 支持二进制数据传输
-
更低的协议开销
-
缺点:
- 需要额外维护连接状态
- 服务端实现复杂度较高
SSE 方案
- 优点:
- 基于 HTTP 协议,兼容现有基础设施
- 自动重连机制
-
更简单的服务端实现
-
缺点:
- 仅支持服务端推送
- 默认 UTF- 8 编码
核心实现方案
异步生成器模拟 API 响应
async function* mockChatGPTStream(prompt: string) {const words = prompt.split('').concat(['Hello','World','!']);
for (const word of words) {
// 模拟网络延迟
await new Promise(resolve => setTimeout(resolve, 50));
yield word;
}
}
优化 DOM 更新策略
- 使用
DocumentFragment批量操作 DOM - 通过
requestAnimationFrame调度渲染任务 - 文本节点复用避免重复创建
const renderStream = async (container: HTMLElement) => {const fragment = document.createDocumentFragment();
const textNode = document.createTextNode('');
fragment.appendChild(textNode);
for await (const chunk of mockChatGPTStream('Hi')) {
textNode.nodeValue += chunk;
// 首次渲染才挂载 DOM
if (!container.contains(fragment)) {requestAnimationFrame(() => {container.appendChild(fragment);
});
}
}
};
完整 TypeScript 实现
class ChatStream {
private abortController: AbortController;
private messageQueue: string[] = [];
private isRendering = false;
constructor(private container: HTMLElement) {this.abortController = new AbortController();
}
async start(prompt: string) {
try {const stream = this.fetchStream(prompt);
for await (const chunk of stream) {if (this.abortController.signal.aborted) break;
this.messageQueue.push(chunk);
this.scheduleRender();}
} catch (err) {console.error('Stream error:', err);
}
}
private async *fetchStream(prompt: string) {
// 实际项目中替换为真实 API 调用
const mockResponse = ['思考中', '...', '你好', '这是', '测试'].values();
for (const word of mockResponse) {await new Promise(r => setTimeout(r, 100));
yield word;
}
}
private scheduleRender() {if (this.isRendering) return;
this.isRendering = true;
requestAnimationFrame(() => {this.renderChunks();
this.isRendering = false;
});
}
private renderChunks() {const fragment = document.createDocumentFragment();
while (this.messageQueue.length) {const textNode = document.createTextNode(this.messageQueue.shift()!);
fragment.appendChild(textNode);
}
this.container.appendChild(fragment);
}
cancel() {this.abortController.abort();
this.messageQueue = [];}
}
性能优化实践
大文本分块策略
- 按句子边界分块(
[.!?]标点分割) - 动态调整分块大小(根据 FPS 监测)
- 优先处理可视区域内容
渲染性能监测
const monitorFPS = () => {let lastTime = performance.now();
let frameCount = 0;
return setInterval(() => {
frameCount++;
const now = performance.now();
if (now - lastTime >= 1000) {console.log(`FPS: ${frameCount}`);
frameCount = 0;
lastTime = now;
}
}, 16);
};
内存泄漏防范
- 及时清理事件监听器
- 取消未完成的异步任务
- 使用 WeakMap 存储 DOM 引用
生产环境避坑指南
网络中断恢复
- 实现断点续传标识(lastEventId)
- 指数退避重试策略
- 本地缓存未完成的消息
敏感词过滤
const filterSensitiveWords = (text: string) => {const bannedWords = ['敏感词 1', '敏感词 2'];
return bannedWords.reduce((result, word) => result.replace(new RegExp(word, 'gi'), '***'),
text
);
};
移动端适配
- 触摸事件中断处理
- 减少 CSS 动画复杂度
- 优化虚拟键盘交互
开放性问题
实现打字机效果速率自适应可考虑:
- 基于网络 RTT 动态调整速度
- 根据用户阅读速度学习偏好
- 内容复杂度分析(长句减速)
- 设备性能自适应(低端设备降频)
正文完
发表至: 前端开发
近一天内
