EventSource 引发的一系列事件 #150

简介: EventSource 引发的一系列事件 #150

背景


大家好,我是江辰,最近小小的实现了下 chatGPT 的问答式回复,调研了前端如何实现这种问答式请求,有几种方案,Http、EventSource、WebSocket,三种实现方案各有优缺点,Http 和 WebSocket ,想必耳大家闻能详,这里我讲讲 EventSource


事件源


EventSource 是服务器推送的一个网络事件接口。一个 EventSource 实例会对 HTTP 服务开启一个持久化的连接,以text/event-stream 格式发送事件,会一直保持开启直到被要求关闭。

一旦连接开启,来自服务端传入的消息会以事件的形式分发至你代码中。如果接收消息中有一个事件字段,触发的事件与事件字段的值相同。如果没有事件字段存在,则将触发通用事件。

WebSockets,不同的是,服务端推送是单向的。数据信息被单向从服务端到客户端分发。当不需要以消息形式将数据从客户端发送到服务器时,这使它们成为绝佳的选择。例如,对于处理社交媒体状态更新,新闻提要或将数据传递到客户端存储机制(如 IndexedDB 或 Web 存储)之类的,EventSource 无疑是一个有效方案。

--- 引自 MDN

对比 WebSocket,它就是简单,方便,在特定的一些场景下,比如聊天消息或市场价格,这就是 EventSource 擅长的

使用方式

它的使用方式极其简单

const evtSource = new EventSource('sse.php');
const eventList = document.querySelector('ul');
evtSource.onmessage = function(e) {
 let newElement = document.createElement("li");
  newElement.textContent = "message: " + e.data;
  eventList.appendChild(newElement);
}

对吧,几行代码搞定,如何携带参数,在 其中 ,就是我们要给链接传的参数new EventSource('sse.php?id=123');id=123

问题来了

当我实现之后,发现它在不断的自动重连?搜了很多文档,想不通,为何会自动重连,这里伏笔。想不通,ok,我就换个思路,改用 Axios 实现


公理


Axios 实现如下

const streamToString = async (readableStream) => {
  return new Promise((resolve, reject) => {
    const chunks = [];
    readableStream.on("data", (data) => {
      chunks.push(data);
    });
    readableStream.on("end", () => {
      resolve(Buffer.concat(chunks).toString('base64'))
    });
    readableStream.on("error", reject);
  });
}
axios({
  method: 'get',
  url:`//xxx/api/chat/stream?prompt=${textarea.current.value.trim()}`,
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  responseType: 'stream'
}).then(async res => {
  const raw = await streamToString(res.data);
})

此时还不知问题的**严重性!**实现完之后,发现不对劲啊,,???(黑人问号脸),遂打印 log 看看输出的 是啥,字符串?根本不是一个方法啊,但看网上实现,是这样啊,没错?又看了几遍,都是这样实现的,很懵,直到看了下 axios 的 issue,传送门,2016年就有人提出了这个问题,也就是说 axios 在浏览器侧一直没有实现 steram,我内心cnm,网上的文档都是假的!!!readableStream.on is not a fucntionres.data

也就是说,按照目前MDN说法, 支持的类型有,,其中 ,此响应类型仅允许用于下载请求,并且仅受 Internet Explorer 支持responseType arraybuffer、blob、document、json、text、ms-streamms-stream

坑坑坑,又要开始了其他方案,想想 Fetch 能不能行,浏览器原生支持哦!


Fetch


Fetch API 提供了一个 JavaScript 接口,用于访问和操纵 HTTP 管道的一些具体部分,例如请求和响应。它还提供了一个全局 fetch() 方法,该方法提供了一种简单,合理的方式来跨网络异步获取资源。

这种功能以前是使用 XMLHttpRequest 实现的。Fetch 提供了一个更理想的替代方案,可以很容易地被其他技术使用,例如 Service Workers。Fetch 还提供了专门的逻辑空间来定义其他与 HTTP 相关的概念,例如 CORS 和 HTTP 的扩展。

--- 引自 MDN

利用 Fetch 实现了如下代码

const response = await fetch(`//xxx/api/chat/stream?prompt=${textarea.current.value.trim()}`);
const reader = response.body.getReader();
const eventList = document.querySelector('ul');
while (true) {
  const { value, done } = await reader.read();
  const utf8Decoder = new TextDecoder('utf-8');
  let data: any = value ? utf8Decoder.decode(value, {stream: true}) : '';
  try {
    data = JSON.parse(data)
    if (data.id || !data.content) {
      return
    }
    let newElement = document.createElement("li");
    newElement.textContent = "message: " + data.content;
    eventList.appendChild(newElement);
  } catch (e) {
  }
  if (done) {
    break;
  }
}

实现没有问题,在我电脑上也跑通了,能稳定接收服务端消息,不会自动重连,万事大吉,转交朋友试用

。。。。

交给朋友试用,反馈说,会出现回复不全???,调试搞起

浏览器侧接收的消息

抓包看的消息

对比看,浏览器侧**丢包!丢包了!!!**几番排查下来,不知为何会丢包,而且是只有 Windows 上会丢包(必现),macOS 上不会,不懂了呀,我们自己测试 Win 下 ping 都是稳定的,有懂的同学,可以告知下,谢谢!


最终解决方案


又回到 EventSource,没错,又回来了,折腾下来发现,每次收完消息,你必须手动关闭下,,才不会自动重连,而且自动重连就是 EventSource 的特性之一,害,伏笔解决了。这个关闭有个前提是,服务端下发字段告诉你,能关闭,你才能关闭哦,折腾啊!!!evtSource.close();


总结


通过这次的学习,让我对 EventSource 以及 Fetch、Axios 有了一次深刻的认知,大家看完觉得还不错的话,欢迎点赞,收藏哦

文章同步更新平台:掘金、CSDN、知乎、思否、博客,公众号(野生程序猿江辰)

我的联系方式,v:Jiang9684,欢迎和我一起学习交流

目录
相关文章
|
JSON 前端开发 API
fetchEventSource源码解析
fetchEventSource源码解析
3175 1
|
存储 JavaScript 前端开发
js数组高阶函数——includes()方法
js数组高阶函数——includes()方法
736 0
|
6月前
|
前端开发 Java 数据库
2025 年 Java 学习完整步骤及详细路线指南
本教程涵盖Java从基础到高级的完整学习路径,包括环境配置、语法基础、面向对象编程、集合框架、多线程、网络编程、数据库操作、Spring Boot、微服务架构及项目实战。通过系统学习与实操,助你全面掌握Java核心技术与企业级应用开发。
1030 0
|
设计模式 数据安全/隐私保护
Next.js 实战 (七):浅谈 Layout 布局的嵌套设计模式
这篇文章介绍了在Next.js框架下,如何处理中后台管理系统中特殊页面(如登录页)不包裹根布局(RootLayout)的问题。作者指出Next.js的设计理念是通过布局的嵌套来创建复杂的页面结构,这虽然保持了代码的整洁和可维护性,但对于特殊页面来说,却造成了不必要的布局包裹。文章提出了一个解决方案,即通过判断页面的skipGlobalLayout属性来决定是否包含RootLayout,从而实现特殊页面不包裹根布局的目标。
397 0
Next.js 实战 (七):浅谈 Layout 布局的嵌套设计模式
|
XML JavaScript 前端开发
技术经验分享:fadeIn()与fadeOut()方法
技术经验分享:fadeIn()与fadeOut()方法
284 0
【最简洁】一句CSS3代码实现不规则自定义背景图拼接样式,多用于异形弹窗背景图
【最简洁】一句CSS3代码实现不规则自定义背景图拼接样式,多用于异形弹窗背景图
|
人工智能
Kimi 高效使用技巧,80%的人都不知道(下)
Kimi 高效使用技巧,80%的人都不知道
|
监控 前端开发 网络协议
SSE(Server-Sent Events)请求与EventSource
SSE(Server-Sent Events)请求与EventSource
2463 0
|
算法 Linux 编译器
【Linux 第三方库】 linux 交叉编译fontconfig,freetype,libxml2,uuid
【Linux 第三方库】 linux 交叉编译fontconfig,freetype,libxml2,uuid
740 0
|
索引
RestHighLevelClient查询所有的索引名称
在Elasticsearch中,使用`RestHighLevelClient`查询所有的索引名称可以通过调用`indices().getAlias(GetAliasesRequest, RequestOptions)`方法并检查返回的响应来实现。虽然这个方法通常用于获取别名,但返回的响应中也包含了索引的元数据,因此我们可以利用这个方法来获取所有的索引名称。 不过,更直接的方法是使用`indices().get(GetRequest, RequestOptions)`方法并请求`_all`索引,或者调用`cat().indices(CatIndicesRequest, RequestOptio
1020 0

热门文章

最新文章