EventSource によって発生する一連のイベント

背景

みなさん、こんにちは。私は Jiang Chen です。最近、chatGPT に少量の質問と回答の応答を実装しました。フロントエンドがこの種の質問と回答のリクエストをどのように実装するかを調査しました。解決策はいくつかあります。 Http、EventSource、および WebSocket。3 つの実装ソリューションにはそれぞれ独自の利点があります。欠点、Http と WebSocket、誰もが聞いたことがあるはずです。ここでは EventSource について説明します。

イベントソース

EventSource は、サーバーによってプッシュされるネットワーク イベント インターフェイスです。EventSource インスタンスは、HTTP サービスへの永続的な接続を開き、テキスト/イベント ストリーム形式でイベントを送信し、閉じるように要求されるまで開いたままになります。

接続が開かれると、サーバーからの受信メッセージがイベントの形式でコードにディスパッチされます。受信したメッセージにイベント フィールドがある場合、トリガーされたイベントはイベント フィールドと同じ値になります。イベント フィールドが存在しない場合は、汎用イベントが発生します。

WebSocketとは異なり、サーバー側のプッシュは一方向です。データ情報はサーバーからクライアントへ一方向に配信されます。そのため、メッセージでクライアントからサーバーにデータを送信する必要がない場合に、これらは優れた選択肢となります。たとえば、EventSource は、ソーシャル メディアのステータス更新やニュース フィードの処理、IndexedDB や Web Storage などのクライアント側のストレージ メカニズムへのデータの受け渡しなどに対して、確かに効果的なソリューションです。

--- 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に渡したいパラメータです。

ここで問題が発生します

image
画像

当我实现之后,发现它在不断的自动重连?搜了很多文档,想不通,为何会自动重连,这里伏笔。想不通,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;
  }
}

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

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

浏览器侧接收的消息 image

抓包看的消息 image

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

最终解决方案

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

总结

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

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

本文由 mdnice 多平台发布

おすすめ

転載: blog.csdn.net/weixin_42439919/article/details/130436133
おすすめ