fastapi返回流式信息,VUE前端EventSource无法在onmessage事件进行接收排查,附fetch接收案例,附带完整代码

发布于:2025-05-22 ⋅ 阅读:(17) ⋅ 点赞:(0)

排查点

  1. 是否是跨域CORS问题
  2. 后端必须用yield产生流式信息,返回类型为fastapi.responses.StreamingResponse,数据格式为data: XXXX \n\n,如果要返回JSON,需要将其字符串化。数据类型为text/event-stream

注意:

  1. 数据格式必须以data开头,以\n\n结尾。如果数据格式没有以data开头,在浏览器网络栏目,EventStrem不会有返回信息,数据信息在响应里面。
    在这里插入图片描述

  2. stream结束后,前端会重连,需要后端发送一个终止标志,前端判断后进行主动关闭。

// 响应事件关闭, 后端需要发送数据格式event: close\ndata: bye\n\n
eventSource.addEventListener("close", (event) => {
  console.log("Received close event");
  eventSource.close();
});

// 判断关闭, 判断关键字
eventSource.onmessage = (event) => {
	console.log("Message:", event.data);
	if (event.data === "[done]") {
		eventSource.close();
		console.log("Closed on 'done' message");
	}
};
  1. 前端接收可以用EventSource也可以用fetch,看完整代码示例

完整代码

fastapi:

@app.get("/stream")  
async def stream_video():  
    async def video_stream():  
        for i in range(10):  
            await asyncio.sleep(3)  
            print(f"i = {i}")  
            yield f"data: Streamed line {i}\n\n"  
        yield "event: close\ndata: bye\n\n"  # Custom event to signal end  
        
    return StreamingResponse(  
        video_stream(),  
        media_type="text/event-stream",  
        headers={  
            "Cache-Control": "no-cache",  
            "X-Accel-Buffering": "no",  
        },  
    )

单脚本vue,直接浏览器打开:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title></title>
    </head>
<body>

<div id="app">
   <div class="flex justify-between px-10 borderd border-1">
        <div class="flex flex-col w-[200px] mt-10">
        
            <button @click="start_stream_1" class="mb-5 bg-teal-700 px-4 py-1 text-white rounded-2xl cursor-pointer">
                start_stream_1
            </button>
            
            <button @click="start_stream_2" class="mb-5 bg-teal-700 px-4 py-1 text-white rounded-2xl cursor-pointer">
                start_stream_2
            </button>

            <div class="w-fit flex justify-between">
                <div class="block w-[500px] h-[300px] whitespace-pre-line p-6 bg-white border border-gray-200 rounded-lg shadow-sm hover:bg-gray-100">
                    {{msgs.content}}
                </div>
                
                
                <div class="ml-10 block w-[500px] h-[300px] whitespace-pre-line p-6 bg-white border border-gray-200 rounded-lg shadow-sm hover:bg-gray-100">
                    {{msgs2.content}}
                </div>
            </div>
            
    </div>
</div>


<script src="https://unpkg.com/@tailwindcss/browser@4"></script>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
    const { createApp, ref, onMounted, reactive  } = Vue;

    createApp({
        setup() {
        
            const msgs = reactive({
                "content": ""
            })
            const msgs2 = reactive({
                "content": ""
            })
        
            const start_stream_1 = async () => {
                const eventSource = new EventSource('http://127.0.0.1:8111/stream');
                   
                eventSource.onopen = function(){
                    alert('connection readyState: '+ eventSource.readyState);  
                };


                // 监听消息事件
                eventSource.onmessage = function(event) {
                    console.log("Received:", event.data);
                    Object.assign(msgs, {"content": msgs.content + "\n" + event.data})
                    //console.log(JSON.parse(event.data)); // 如果fastapi返回json字符串
                };
                
                eventSource.addEventListener("close", (event) => {
                    console.log("Received close event");
                    Object.assign(msgs, {"content": msgs.content + "\nfinish receive"})
                    eventSource.close();
                });
 
            }
            
            const start_stream_2 = async () => {
                const eventSource = new EventSource('http://127.0.0.1:8111/stream');
                   
                fetch("http://127.0.0.1:8111/stream")
                .then(response => {
                    const reader = response.body.getReader();
                    const decoder = new TextDecoder();

                    function read() {
                        reader.read().then(({ done, value }) => {
                            if (done) {
                                Object.assign(msgs2, {"content": msgs.content + "\nfinish receive"})
                                return;
                            }

                            const chunk = decoder.decode(value, { stream: true });
                            console.log("Chunk:", chunk); // Handle/display chunk
                            Object.assign(msgs2, {"content": msgs.content + "\n" + chunk})
                            read(); // Continue reading
                        });
                    }

                    read();
                })
                .catch(err => console.error("Fetch error:", err));
            }
 

            onMounted(() => {
                console.log("on mounted")
            })
            

            return {
                start_stream_1,
                start_stream_2,
                msgs,
                msgs2
            };
        }
    }).mount('#app');
</script>

</body>
</html>


网站公告

今日签到

点亮在社区的每一天
去签到