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

如何在NextJS API端点增量返回搜索结果?

解决方案

核心问题分析

  1. 顺序错误:你用for...of遍历taskPromises数组,本质是按任务定义的顺序依次等待结果,而非按任务实际完成的顺序返回。
  2. 分块合并:缺少正确的响应头提示,导致浏览器/NextJS缓冲所有输出后一次性返回;同时当前的stream配置没有强制禁用缓冲。
  3. 无效JSON:直接输出多个JSON对象会导致整体无效,应该采用**NDJSON(换行分隔JSON)**格式,每个分块是独立的JSON对象,用换行分隔,既符合JSON规范,又支持增量解析。

修改后的代码

export async function GET(request) {
    const searchParams = request.nextUrl.searchParams
    const query = searchParams.get('q')
    const year = searchParams.get('year')

    // 定义并行任务
    const tasks = [
        async () => ({ type: 'coordinates', result: await parseCoordinates(query) }),
        async () => ({ type: 'address', result: await runAddressSearch(query) }),
        async () => ({ type: 'metro', result: await runMetroSearch(year, query) }),
        async () => ({ type: 'road', result: await runRoadSearch(year, query) }),
        async () => ({ type: 'district', result: await runDistrictSearch(year, query) }),
    ];

    // 转换为带标识的Promise,方便后续跟踪
    const pendingPromises = tasks.map((task, index) => 
        task().then(result => ({ status: 'fulfilled', type: result.type, data: result.result }))
              .catch(error => ({ status: 'rejected', type: tasks[index].name, error: error.toString() }))
    );

    const stream = new ReadableStream({
        async start(controller) {
            const encoder = new TextEncoder();
            // 循环处理所有完成的Promise,直到没有待处理任务
            while (pendingPromises.length > 0) {
                // 等待第一个完成的Promise
                const result = await Promise.race(pendingPromises);
                // 从待处理列表中移除已完成的Promise
                const promiseIndex = pendingPromises.findIndex(p => p === result);
                if (promiseIndex !== -1) {
                    pendingPromises.splice(promiseIndex, 1);
                }

                // 生成NDJSON格式的分块
                let chunk;
                if (result.status === 'fulfilled') {
                    if (result.data.length > 0) {
                        chunk = JSON.stringify({ type: result.type, result: result.data }) + '\n';
                    }
                } else {
                    chunk = JSON.stringify({ type: result.type, error: result.error }) + '\n';
                }

                // 写入流
                if (chunk) {
                    controller.enqueue(encoder.encode(chunk));
                    // 强制触发流的刷新(部分环境需要)
                    await new Promise(resolve => setTimeout(resolve, 0));
                }
            }
            controller.close();
        },
        // 配置流禁用缓冲
        highWaterMark: 1,
        size() {
            return 1;
        }
    });

    // 设置正确的响应头,告诉客户端是分块的NDJSON
    return new Response(stream, {
        headers: {
            'Content-Type': 'application/x-ndjson',
            'Transfer-Encoding': 'chunked',
            'Cache-Control': 'no-cache',
            'X-Content-Type-Options': 'nosniff'
        }
    });
}

关键修改点说明

  1. 按完成顺序返回:
    • 用Promise.race监听所有待处理Promise,每次取最先完成的结果
    • 处理完成后从pendingPromises列表中移除该Promise,确保后续只处理剩余任务
  2. 避免分块合并:
    • 添加Transfer-Encoding: chunked头,明确告知服务器采用分块传输
    • 设置Cache-Control: no-cache禁止缓冲
    • 加入await new Promise(resolve => setTimeout(resolve, 0))强制让出事件循环,确保分块及时推送
  3. 有效JSON格式:
    • 使用application/x-ndjson作为Content-Type,客户端可按行解析每个JSON块
    • 每个分块末尾添加换行符,保证单个JSON块的独立性

客户端解析示例(可选)

如果需要在客户端处理流式返回的NDJSON,可以这样实现:

async function fetchSearchResults(query) {
    const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`);
    const reader = response.body.getReader();
    const decoder = new TextDecoder();
    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(); // 保留不完整的最后一行

        for (const line of lines) {
            if (line.trim()) {
                const result = JSON.parse(line);
                // 处理单个结果
                console.log(result.type, result.result || result.error);
            }
        }
    }
}

内容的提问来源于stack exchange,提问作者Nick K9

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 08:40:55