在传统的前端开发模式下,数据通常是批量加载,等待所有数据加载完成后再一次性呈现给用户。然而,随着大数据和实时应用的崛起,这种一次性加载的方式往往会导致页面加载时间过长,用户体验大打折扣。流式输出则提供了一种全新的解决方案,它允许数据分批加载并在接收到每一部分数据时立即更新视图,从而显著提升了页面响应速度和用户满意度。在接下来的内容中,我将讲述如何在Vue2中,利用SSE技术来实现流式输出。
流式输出是一种数据处理和传输方式,它允许数据源连续不断地向接收端发送数据,而无需等待整个数据集准备完毕。在流式输出中,数据可以像水流一样源源不断地传输。在这种模式下,用户可以迅速开始观看或监听,而无需等到所有数据都加载完毕。
1、SSE技术介绍
SSE (Server-Sent Events)即服务器推送事件,是一种基于HTTP的服务器推送技术。它允许服务器实时地向客户端推送数据。在SSE中,客户端通过创建一个EventSource对象来监听来自服务器的事件流。一旦连接建立,服务器就可以开始发送事件到客户端。客户端可以通过监听特定的事件类型来处理这些消息,并在需要时执行相应的操作。
SSE工作原理
1、建立连接:
客户端通过在HTTP请求中设置Accept: text/event-stream来表明其期望接收SSE。
服务器响应一个持久连接,通常使用HTTP的Content-Type: text/event-stream和Connection: keep-alive头。
2、数据传输:
服务器通过这个连接发送事件流,每条事件由三部分组成:
• event字段:可选,用于标识事件类型。
• data字段:必需,包含实际的数据内容。
• id字段:可选,用于客户端确认消息的顺序或重播。
每条事件之间用换行符分隔,每个字段之间用冒号和空格分隔。
示例事件流:
event: myEvent
data: {"message": "Hello, World!"}
id: 123
3、客户端处理:
浏览器会自动处理接收到的事件流,并触发一个eventSource对象的message事件。开发者可以注册事件监听器来处理这些事件。
2、数据准备
本文后端的数据来源是使用Python代码,基于Flask框架进阶实现。
在此仅仅强调四点。
1、 返回数据response的响应头中的Content-Type应该设置成text/event-stream。
2、返回数据response的响应头中的Cache-Control应该设置成no-cache。
3、 数据响应要有一个结尾标识"done"。以便于数据流断开链接,避免重复发起请求。
4、 跨域尽量再后端进行处理,如果跨域再前端处理的话,会出现数据等待,进而无法实现流式输出的效果。
3、关键方法实现
connectToSSE() {
this.eventSource = new EventSource('http://127.0.0.1:5000/llm/request');
this.eventSource.onmessage = (event) => {
const data = JSON.parse(event.data)//将字符串转换成json格式
if (data.number !== 'done') {
this.numbers.push(data.number);
} else {
this.eventSource.close();
}
};
this.eventSource.onerror = (error) => {
if (this.eventSource.readyState === EventSource.CLOSED) {
console.log('Connection to server closed');
} else {
console.error('Error occurred:', error);
}
};
}
4、代码解析
- 1、使用new EventSource(url)创建对象。url表示请求数据的地址。
- 2、this.eventSource.onmessage = (event) => {}当服务器发送新的SSE事件时,onmessage
- 事件处理器会被触发。我们在这里对于业务逻辑进行主要编写。比如数据对象的补充。
- 3、如果返回的数据为结束标识符"done",则关闭与SSE源的连接。避免请求多发和资源浪费。
- 4、this.eventSource.onerror = (error) =>{}如果SSE连接出现错误,onerror
事件处理器会被触发。及时的关闭数据流,避免长时间的等待,占用内存。
-5、F12,看一下具体请求头和响应头信息。
6、完整代码
<template>
<div style="display: flex">
<div v-for="(number, index) in numbers" :key="index"> {{ number }} </div>
</div>
</template>
<script>
export default {
data() {
return {
numbers: [],
eventSource: null,
};
},
created() {
this.connectToSSE();
},
methods: {
connectToSSE() {
this.eventSource = new EventSource('http://127.0.0.1:5000/llm/request');
this.eventSource.onmessage = (event) => {
const data = JSON.parse(event.data) //将字符串转换成json格式
if (data.number !== 'done') { //判断结束标识
this.numbers.push(data.number);
} else {
this.eventSource.close();
}
};
this.eventSource.onerror = (error) => {
if (this.eventSource.readyState === EventSource.CLOSED) {
console.log('Connection to server closed');
} else {
console.error('Error occurred:', error);
}
};
}
},
beforeDestroy() {
if (this.eventSource) {
this.eventSource.close();
}
},
};
</script>