前端之入坑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) 技术在单服务多实例副本上的实现

相关推荐
binqian14 分钟前
【异步】js中异步的实现方式 async await /Promise / Generator
开发语言·前端·javascript
前端李二牛1 小时前
异步任务并发控制
前端·javascript
你也向往长安城吗1 小时前
推荐一个三维导航库:three-pathfinding-3d
javascript·算法
karrigan1 小时前
async/await 的优雅外衣下:Generator 的核心原理与 JavaScript 执行引擎的精细管理
javascript
wycode1 小时前
Vue2实践(3)之用component做一个动态表单(二)
前端·javascript·vue.js
老顾聊技术1 小时前
老顾深度解析【字节跳动的AI项目DeerFlow】源码之人工中断(四)
llm·agent
wycode2 小时前
Vue2实践(2)之用component做一个动态表单(一)
前端·javascript·vue.js
第七种黄昏2 小时前
Vue3 中的 ref、模板引用和 defineExpose 详解
前端·javascript·vue.js
我是哈哈hh3 小时前
【Node.js】ECMAScript标准 以及 npm安装
开发语言·前端·javascript·node.js
张元清3 小时前
电商 Feeds 流缓存策略:Temu vs 拼多多的技术选择
前端·javascript·面试