Una secuencia de eventos generados por EventSource.

fondo

Hola a todos, soy Jiang Chen. Recientemente, implementé una pequeña cantidad de respuestas de preguntas y respuestas en chatGPT. He investigado cómo el front-end implementa este tipo de solicitudes de preguntas y respuestas. Hay varias soluciones. Http, EventSource y WebSocket. Las tres soluciones de implementación tienen sus propias ventajas. Desventajas, Http y WebSocket, todos deben haber oído hablar de ello, aquí hablaré sobre EventSource.

EventSource

EventSource es una interfaz de eventos de red impulsada por el servidor. Una instancia de EventSource abrirá una conexión persistente al servicio HTTP, enviará eventos en formato de texto/flujo de eventos y permanecerá abierta hasta que se le solicite cerrarla.

Una vez que se abre la conexión, los mensajes entrantes del servidor se envían a su código en forma de eventos. Si hay un campo de evento en el mensaje recibido, el evento desencadenado tiene el mismo valor que el campo de evento. Si no hay ningún campo de evento presente, se activará el evento genérico.

A diferencia de WebSockets , la inserción del lado del servidor es unidireccional. La información de los datos se distribuye unidireccionalmente del servidor al cliente. Esto los convierte en una excelente opción cuando no es necesario enviar datos del cliente al servidor en mensajes. Por ejemplo, EventSource es sin duda una solución eficaz para cosas como gestionar actualizaciones de estado de redes sociales, fuentes de noticias o pasar datos a mecanismos de almacenamiento del lado del cliente como IndexedDB o Web Storage.

--- Citado de MDN

En comparación con WebSocket, es simple y conveniente . En algunos escenarios específicos, como mensajes de chat o precios de mercado, esto es lo que hace bien EventSource.

Cómo utilizar

Es extremadamente simple de usar.

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

Bien, se hicieron algunas líneas de código, cómo transportar parámetros, entre new EventSource('sse.php?id=123');ellos id=123, están los parámetros que queremos pasar al enlace.

aquí viene el problema

image
imagen

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

Supongo que te gusta

Origin blog.csdn.net/weixin_42439919/article/details/130436133
Recomendado
Clasificación