乐趣区

关于前端:EventSource-引发的一系列事件

背景

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

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

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);
})

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

也就是说,依照目前 MDN 说法,responseType 反对的类型有,arraybuffer、blob、document、json、text、ms-stream,其中 ms-stream,此响应类型仅容许用于下载申请,并且仅受 Internet Explorer 反对

坑坑坑,又要开始了其余计划,想想 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,没错,又回来了,折腾下来发现,每次收完音讯,你必须手动敞开下,evtSource.close();,才不会主动重连,而且主动重连就是 EventSource 的个性之一,害,伏笔解决了。这个敞开有个前提是,服务端下发字段通知你,能敞开,你能力敞开哦,折腾啊!!!

总结

通过这次的学习,让我对 EventSource 以及 Fetch、Axios 有了一次粗浅的认知,大家看完感觉还不错的话,欢送点赞,珍藏哦
文章同步更新平台:掘金、CSDN、知乎、思否、博客,公众号(家养程序猿江辰)

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

退出移动版