常见前端面试题 之 AI打字机效果是如何实现的?

之前面试一个 AI 企业的时候面试官问我如何实现像 ChatGPT 的打字机效果,我当时的回答是用流式的方案,但具体不知道有哪些实现的方案,在这里我研究一下打字机的实现方案。

也欢迎大家使用我的本地图片压缩工具 limgx.com

分析效果

使用 AI 对话的时候,文字是一个字一个字蹦出来的?它不是等所有内容生成完毕后一次性展示,而是以一种流畅、实时的"打字机"效果呈现,我们用传统的 http 轮询和 websocket 都是很难实现如此流畅无延迟的效果的,这种体验的背后,隐藏着一种高效的Web技术。它就是服务器推送事件(Server-Sent Events,简称 SSE)

什么是 SSE

从根本上说,SSE 是一种允许服务器单向持续地向客户端(浏览器)推送数据的Web技术。

可以把它想象成一个电台广播

  • 服务器是广播电台,持续不断地播出节目。
  • 客户端是收音机,一旦调到正确的频道,就能持续收听,无需反复请求"现在有什么新节目吗?"。

SSE 仍然是 HTTP 协议,它建立一个从服务器到客户端的长连接,服务器可以随时通过这个连接发送数据,而客户端只需被动接收即可。

如何使用

SSE 的实现非常简洁,其核心流程如下:

发起连接 :浏览器用 new EventSource('/your-stream-endpoint') 创建一个实例,给服务器发一个 HTTP GET 请求。

保持连接 :服务器收到请求后,发送带特殊响应头 Content-Type: text/event-stream 的响应,并保持该连接开启

发送数据 :服务器以特定的文本格式(data: ...\n\n)将事件和数据块发送给客户端

实现流式聊天应用

用Bun启动一个本地服务器,实现sse接口

代码如下

javascript 复制代码
import { serve } from "bun";
import PageApp from "./chat.html";
serve({
  port: 3000,
  routes: {
    "/api/chat": {
      async GET(req) {
        const url = new URL(req.url);
        const message = url.searchParams.get('message');
        if (!message) return new Response("消息不能为空", { status: 400 });
​
        // 创建 SSE 流
        const stream = new ReadableStream({
          async start(controller) {
            const response = `man,hahahaha,what can I say,mambaout!`;
​
            for (const char of response) {
              const data = `data: ${JSON.stringify({ content: char })}\n\n`;
              controller.enqueue(new TextEncoder().encode(data));
              await new Promise((resolve) => setTimeout(resolve, 50));
            }
            controller.close();
          },
        });
​
        return new Response(stream, {
          headers: {
            "Content-Type": "text/event-stream",
            "Cache-Control": "no-cache",
          },
        });
      },
    },
    "/": PageApp,
  },
  async fetch(req) {
    return new Response("404", { status: 404 });
  },
});
​
console.log("http://localhost:3000");
​
​

前端代码

ini 复制代码
// 连接 SSE
const eventSource = new EventSource(`/api/chat?message=${encodeURIComponent(message)}`);

eventSource.onmessage = (event) => {
    const data = JSON.parse(event.data);
    botDiv.textContent += data.content;
};
​
eventSource.onerror = () => {
    eventSource.close();
};

实际效果

打开浏览器控制台可以看到消息是一个字符一个字符发送的

它的消息通常还有以下字段 核心字段

  • data: 这是消息的数据载荷。例如: data: {"price": 123.45, "time": "14:30:00"}
  • event: 自定义事件的名称。如果没有这个字段,默认就是 message 事件。例如: event: stock_update
  • id: 事件的唯一标识符。
  • retry: 告知浏览器在断线重连前应等待多少毫秒。
  • : (冒号开头的行): 这是注释行,会被客户端忽略。一个常见的用途是作为"心跳包",防止因网络长时间无活动而被某些防火墙或代理中断连接。

总结

所以,SSE 的原理可以归结为:

  1. 利用 HTTP 的灵活性 :通过特定的 Content-Type 改变了 HTTP 请求的"一次性"行为模式,将其转变为持久连接。
  2. 定义简单的文本协议 :规定了 data, id, event 等字段和 \n\n 分隔符,使得数据传输既简单又可扩展。
  3. 在浏览器端内置智能 :将连接管理、数据解析和自动重连+断点续传 等复杂逻辑封装在 EventSource API 中,极大地简化了开发者的工作。

它并非发明了一个全新的网络协议,而是对现有 Web 基础设施的一次巧妙"再利用",因此具有极好的兼容性和易用性。

相关推荐
时光足迹3 分钟前
Tiptap 简单编辑器模版
前端·javascript·react.js
吴声子夜歌14 分钟前
Vue3——使用Mock.js
javascript·vue·mock.js
JSLove17 分钟前
nginx入门
前端·nginx
时光足迹18 分钟前
ThreeJS之GUI控制器
前端·javascript·three.js
时光足迹19 分钟前
Tiptap编辑器
前端·javascript·react.js
im_AMBER20 分钟前
手撕hot100之矩阵!看完这篇就AC~
javascript·数据结构·线性代数·算法·leetcode·矩阵
时光足迹22 分钟前
电子书阅读器之笔记高亮(跨段处理)
前端·javascript·react.js
Dabei25 分钟前
Android 副屏(Virtual Display)创建与悬浮窗画中画显示实战
前端·架构
Hello-Mr.Wang44 分钟前
【保姆级教程】MasterGo MCP + Cursor 一键实现 UI 设计稿还原
前端·javascript·vue.js·ai编程