常见前端面试题 之 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 基础设施的一次巧妙"再利用",因此具有极好的兼容性和易用性。

相关推荐
TeleostNaCl1 小时前
解决 Chrome 无法访问网页但无痕模式下可以访问该网页 的问题
前端·网络·chrome·windows·经验分享
前端大卫3 小时前
为什么 React 中的 key 不能用索引?
前端
你的人类朋友3 小时前
【Node】手动归还主线程控制权:解决 Node.js 阻塞的一个思路
前端·后端·node.js
小李小李不讲道理4 小时前
「Ant Design 组件库探索」五:Tabs组件
前端·react.js·ant design
毕设十刻5 小时前
基于Vue的学分预警系统98k51(程序 + 源码 + 数据库 + 调试部署 + 开发环境配置),配套论文文档字数达万字以上,文末可获取,系统界面展示置于文末
前端·数据库·vue.js
mapbar_front5 小时前
在职场生存中如何做个不好惹的人
前端
牧杉-惊蛰6 小时前
纯flex布局来写瀑布流
前端·javascript·css
一袋米扛几楼987 小时前
【软件安全】什么是XSS(Cross-Site Scripting,跨站脚本)?
前端·安全·xss
向上的车轮7 小时前
Actix Web适合什么类型的Web应用?可以部署 Java 或 .NET 的应用程序?
java·前端·rust·.net
XiaoYu20027 小时前
第1章 核心竞争力和职业规划
前端·面试·程序员