前端之入坑SSE那些事

好记性不如烂笔头。

1、前言

大家好,我是黑翼。写得不好,不喜勿喷。

2、背景

本篇博客的背景,来自于工作中的大模型业务需求。作为该需求的前端开发,在完成工作过程中,学习了很多,也遇见了一些坑,因此记录并分享。

3、SSE

在今年ChatGPT爆火之后,SSE这门通信技术也随之得到了更多的关注。之所以其在大模型中应用得比较多,就是因为'打字机'效果,让用户觉得交互体验比较好。当然,使用SSE的原因也不仅仅如此。

我们选择SSE方案有以下原因:

  • 1、SSE比较轻,相较于Websocket;
  • 2、基于Http协议;
  • 3、是流式、单工(服务端 => 客户端)的;
  • 4、可以用fetch实现;
  • 5、向chatgpt等平台看齐;

4、兼容性

SSE具有广泛的的浏览器兼容性,除IE之外的浏览器几乎均支持。

5、fetch实现

看了部分文章以及通过chrome调试工具观察某chatgpt助手,发现使用fetch即可实现流式交互。其实,对于前端而言,目前用的更广泛还是axios库,对axios也可能了解更多一些,所以完成该需求,也让我对fetch有了更深的理解。。

本节部分会先提一些知识点,最后会附上完成(脱敏)代码。

5.1 配置

配置包括前端配置、服务端配置,本质是浏览器发起 http 请求,服务器在收到请求后,流式返回状态与数据后进行处理,实现'打字机'效果。

前端配置主要包括以下:

js 复制代码
let options = {  
    method: 'POST',  
    headers: {  
        'Content-Type': 'application/json',  
        Accept: 'text/event-stream',  
        'Sec-Fetch-Site': 'same-origin',  
    },  
    credentials: 'include',  
    body: JSON.stringify({  
      data  
    }),  
};  

Accept:SSE API规定推送事件流的 MIME 类型为 text/event-stream。

credentials: 携带cookie凭证;

而服务端配置主要包括(但不限于)以下:

js 复制代码
proxy_http_version 1.1;  
proxy_set_header Connection '';  
proxy_buffering off;  
proxy_cache off;  
gzip off;  
chunked_transfer_encoding off;  
keepalive timeout 300  

如果上述服务器配置还未能支持'流式交互',在确定不是代码问题之后,务必关注这一点。

个人经历:如果后端使用了一些公司内部组件的话,需要排查该组件是否会导致'流式交互'失败。

5.2 消息格式

EventStream(事件流)为 UTF-8 格式编码的文本或使用 Base64 编码和 gzip 压缩的二进制消息。

每次推送,可由多个消息组成,每个消息之间以空行分隔(即最后一个字段以\n\n结尾)。

这个是比较重要的特点,方便解析每条推送消息,实现'打字机'效果。

5.3 Complete

当后端将所有数据推送完后,需要向前端推送一条Complete消息,浏览器监听该消息后将done置为true,表示整个响应完整结束了。

如果不发送complete消息,浏览器会报错 - net::ERR_INCOMPLETE_CHUNKED_ENCODING 200 (OK)。

5.4 timeout

由于面向的是业务需求,而且fetch的超时与axios的超时也有所区别,所以这里单独提一下。

与axios不同,fetch的超时使用的是AbortController,具体见后续代码。

5.5 完整代码

js 复制代码
const controller = new AbortController();  
const timeout = setTimeout(() => {  
    controller.abort();   
    console.error('查询超时,取消请求!');  
}, 1000 * 60 * 3);
const options = {  
    method: 'POST',  
    headers: {  
        'Content-Type': 'application/json',  
        Accept: 'text/event-stream'
    },  
    credentials: 'include',  
    body: JSON.stringify({  
        data
    }),  
    signal: controller.signal,  
    timeout,  
};
this.fetchEventSource(this.SSEUrl, options);

fetchEventSource(url, options) {  
    fetch(url, options)  
        .then((response) => {  
            const reader = response.body.getReader();  
            let buffer = '';  
            const decoder = new TextDecoder();  
            const read = () => {  
                return reader.read().then(({ done, value }) => {  
                    options.timeout && clearTimeout(options.timeout);  
                    if (done) {  
                        return;  
                    }  
                    buffer += decoder.decode(value);  
                    const parts = buffer.split('\n\n');  
                    buffer = parts.pop() || ''; // 保留不完整的部分  
                    parts.forEach(part => {  
                        this.parseSSEEvent(part);  
                    });  
                    // 继续读取下一个数据块  
                    read();  
                });  
            };  
            // 开始读取数据流  
            read();  
        })  
        .catch(() => {  
            options.timeout && clearTimeout(options.timeout);  
        });  
}  
parseSSEEvent(data: any) {  
    try {  
        data.split('\n').forEach(line => {  
           // 处理数据
           ....
        });  
    } catch (error) {   
        console..error('数据解析失败!');  
    }  
}

6、总结

在接触新技术、新需求时,'摸着石头过河'可能会是比较常见的情形。因此,在前行的路上,不妨多回头看,做一些阶段性总结,记录一些经历。总之,好记性不如烂笔头

7、参考

一文读懂即时更新方案:SSE

Server-Send-Event (SSE) 技术在单服务多实例副本上的实现

相关推荐
子非鱼92133 分钟前
【前端】ES6:Set与Map
前端·javascript·es6
想退休的搬砖人1 小时前
vue选项式写法项目案例(购物车)
前端·javascript·vue.js
啥子花道1 小时前
Vue3.4 中 v-model 双向数据绑定新玩法详解
前端·javascript·vue.js
麒麟而非淇淋1 小时前
AJAX 入门 day3
前端·javascript·ajax
茶茶只知道学习1 小时前
通过鼠标移动来调整两个盒子的宽度(响应式)
前端·javascript·css
清汤饺子1 小时前
实践指南之网页转PDF
前端·javascript·react.js
蒟蒻的贤1 小时前
Web APIs 第二天
开发语言·前端·javascript
清灵xmf2 小时前
揭开 Vue 3 中大量使用 ref 的隐藏危机
前端·javascript·vue.js·ref
蘑菇头爱平底锅2 小时前
十万条数据渲染到页面上如何优化
前端·javascript·面试
2301_801074152 小时前
TypeScript异常处理
前端·javascript·typescript