You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

FastAPI+React实现JSON响应渐进式加载方案咨询

流式传输实现方案:渐进返回JSON对象供React前端渲染

针对你的两层后端架构(认证转发后端 + 生成结果的微服务),需要逐个返回JSON对象让前端渐进渲染,以下是两种思路的落地实现及补充方案:

一、Server-Sent-Events (SSE) 方案

微服务端实现(生成SSE流)

用sse-starlette直接生成SSE事件流,每个事件携带单个JSON对象:

from fastapi import FastAPI
from sse_starlette.sse import EventSourceResponse
import asyncio
import json

app = FastAPI()

async def generate_results():
    # 模拟耗时的结果生成逻辑
    for i in range(5):
        await asyncio.sleep(1)
        result = {"id": i, "data": f"result_{i}"}
        # 自定义event类型,方便前端区分事件
        yield {"event": "result", "data": json.dumps(result)}

@app.get("/stream-sse")
async def stream_sse():
    return EventSourceResponse(generate_results())

转发后端实现(代理SSE流)

用aiohttp作为SSE客户端请求微服务,转发事件的同时处理认证:

from fastapi import FastAPI, Request
from sse_starlette.sse import EventSourceResponse
import aiohttp

app = FastAPI()

async def proxy_sse(request: Request):
    # 提取前端请求中的认证凭证(如Authorization头)
    auth_header = request.headers.get("Authorization")
    async with aiohttp.ClientSession() as session:
        async with session.get(
            "http://微服务地址/stream-sse",
            headers={"Authorization": auth_header} if auth_header else {}
        ) as resp:
            # 逐行读取微服务的SSE事件并直接转发
            async for line in resp.content:
                if line.strip():
                    yield line.decode("utf-8")

@app.get("/proxy-sse")
async def proxy_stream(request: Request):
    return EventSourceResponse(proxy_sse(request))

React前端处理

用浏览器原生EventSource监听事件,拿到结果立即更新渲染:

import { useEffect, useState } from 'react';

function ResultList() {
    const [results, setResults] = useState([]);

    useEffect(() => {
        const eventSource = new EventSource('/proxy-sse');
        eventSource.addEventListener('result', (event) => {
            const result = JSON.parse(event.data);
            setResults(prev => [...prev, result]);
        });

        return () => eventSource.close();
    }, []);

    return (
        <ul>
            {results.map(item => (
                <li key={item.id}>{item.data}</li>
            ))}
        </ul>
    );
}

二、NDJSON格式的StreamingResponse方案

微服务端实现(返回换行分隔的JSON流)

用换行符分隔单个JSON对象(NDJSON格式),避免复杂字节流解析:

from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import asyncio
import json

app = FastAPI()

async def generate_ndjson():
    for i in range(5):
        await asyncio.sleep(1)
        result = {"id": i, "data": f"result_{i}"}
        # 每个JSON对象后加换行符作为分隔
        yield f"{json.dumps(result)}\n"

@app.get("/stream-ndjson")
async def stream_ndjson():
    return StreamingResponse(generate_ndjson(), media_type="application/x-ndjson")

转发后端实现(代理NDJSON流)

直接逐行读取微服务响应并转发,无需额外解析:

from fastapi import FastAPI, Request
from fastapi.responses import StreamingResponse
import aiohttp

app = FastAPI()

async def proxy_ndjson(request: Request):
    auth_header = request.headers.get("Authorization")
    async with aiohttp.ClientSession() as session:
        async with session.get(
            "http://微服务地址/stream-ndjson",
            headers={"Authorization": auth_header} if auth_header else {}
        ) as resp:
            async for line in resp.content:
                yield line

@app.get("/proxy-ndjson")
async def proxy_stream(request: Request):
    return StreamingResponse(proxy_ndjson(request), media_type="application/x-ndjson")

React前端处理

通过Fetch API的ReadableStream逐块读取,分割换行符解析单个JSON:

import { useEffect, useState } from 'react';

function ResultList() {
    const [results, setResults] = useState([]);

    useEffect(() => {
        async function fetchStream() {
            const response = await fetch('/proxy-ndjson');
            const reader = response.body.getReader();
            const decoder = new TextDecoder('utf-8');
            let buffer = '';

            while (true) {
                const { done, value } = await reader.read();
                if (done) break;
                
                buffer += decoder.decode(value);
                // 按换行分割,处理完整的JSON行
                const lines = buffer.split('\n');
                buffer = lines.pop(); // 保留未完成的最后一行
                
                lines.forEach(line => {
                    if (line) {
                        const result = JSON.parse(line);
                        setResults(prev => [...prev, result]);
                    }
                });
            }
        }

        fetchStream();
    }, []);

    return (
        <ul>
            {results.map(item => (
                <li key={item.id}>{item.data}</li>
            ))}
        </ul>
    );
}

方案对比与选择

  • SSE方案:自带重连机制,协议语义清晰,前端实现更简单,适合单向推送场景。
  • NDJSON StreamingResponse:兼容性更广(无需考虑SSE的浏览器支持限制),可用常规HTTP请求处理,适合需要灵活控制流的场景。

若前端目标浏览器均为现代浏览器,优先选SSE;若需兼容更多环境或偏好常规Fetch API,选NDJSON方案。

内容的提问来源于stack exchange,提问作者Shiladitya Bose

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 17:03:38