js流式模式输出 函数模式使用

一、传统模式:一次性返回完整结果

我们平时写接口大多是这样:

复制代码
async function getAnswer(question: string): Promise<string> {
  const result = await callAI(question); // 等待完整结果(可能需要几秒)
  return result; // 一次性返回
}

// 控制器中
const answer = await service.getAnswer(question);
ctx.body = { data: answer };

如果开发 ai 相关的项目

问题:用户必须等待 AI 生成完整个回答才能看到内容。如果 AI 生成需要 10 秒,用户就白屏等待 10 秒,体验很差。

二、流式模式:边生成边返回

我们希望 AI 每生成一个字,就立刻推送给用户。但 HTTP 响应只能发送一次,不能先返回一部分再返回另一部分。解决办法是:

  • 不关闭连接,保持 HTTP 响应通道开启,持续写入数据块(SSE 或分块传输)。

  • 后端需要一种方式,能够"每产生一个 token 就调用一个函数"来写入数据。

这就是 回调函数模式 的应用场景。

三、回调函数模式模拟

想象你有一个函数 generate,它模拟 AI 逐字产生结果:

复制代码
// 模拟 AI 逐字生成
function simulateAI(word: string, callback: (token: string) => void) {
  for (const ch of word) {
    setTimeout(() => callback(ch), 100); // 每 100ms 输出一个字符
  }
}

如果不用回调,我们可能会写:

复制代码
function generateSync(word: string): string {
  let result = '';
  for (const ch of word) {
    result += ch;
    // 无法在这里把中间结果返回给调用方,因为函数还没结束
  }
  return result; // 只能最后一次性返回
}

使用回调后,调用方可以这样:

复制代码
simulateAI('你好', (token) => {
  console.log('收到 token:', token); // 会打印三次:你、好
});

回调函数 (token) => { ... } 就像是"收到数据时的处理指令"。simulateAI 每产生一个字,就执行这个指令,把字传出去。

代码中的实际应用

BusinessChatService.chatStream 中:

复制代码
async chatStream(input: string, onToken: (token: string) => void) {
  // ... 调用 LM Studio 流式 API
  for await (const chunk of stream) {
    // 解析出 token
    const token = parsed.choices[0]?.delta?.content;
    if (token) {
      onToken(token); // 每得到一个 token,就调用外部传入的回调
    }
  }
}

控制器调用时:

复制代码
await service.chatStream(input, (token) => {
  res.write(`data: ${JSON.stringify({ token })}\n\n`); // 将 token 写入 HTTP 响应
});

关键点

  • chatStream 不关心 token 最终被写到哪里(可以是 HTTP 响应、WebSocket、文件等),它只负责"产生 token 时调用回调"。

  • 控制器负责提供"写入响应"的具体实现(即回调函数)。

  • 这样实现了职责分离:服务专注于 AI 交互,控制器专注于网络传输。

  • 在传统 CRUD 开发中,我们通常直接 return 数据库查询结果,很少需要"逐行返回"。

  • 流式输出是 AI 应用特有的需求,需要改变思维:从"等待完整结果"到"边生成边推送"。

  • 回调函数通常用于事件驱动(如 addEventListener),但在这里用于异步迭代器的数据推送。

六、类比帮助你理解

想象你在厨房做菜(AI 生成回答),服务员(控制器)站在门口等。

  • 传统模式:你把整盘菜做好后才端出去,顾客饿着肚子等很久。

  • 流式模式:你做一道菜(一个字)就让服务员立刻端出去,顾客边吃边等。

服务员怎么知道菜做好了?你每完成一道菜就喊一声"上菜!"(调用回调函数)。服务员听到后就把菜端走(写入 HTTP 响应)。

在你的代码中:

  • "你" = chatStream 方法

  • "上菜!" = onToken(token)

  • "服务员" = 控制器中传入的箭头函数

七、总结

  • 回调函数模式 在这里是为了实现流式输出,避免用户等待完整结果。

  • chatStream 不返回结果,而是通过反复调用 onToken 来"推送"数据块。

  • 控制器负责提供"如何推送"的具体逻辑(写入 HTTP 响应)。

  • 这种模式对于 AI 对话、实时数据处理非常实用。


相关推荐
无风听海9 小时前
OAuth 2.0 response_type完全指南
java·开发语言·oauth
Cyan_RA99 小时前
SpringMVC 数据格式化处理 详解
java·开发语言·spring·mvc·ssm·springmvc·数据格式化
测试员周周9 小时前
【Appium 系列】第08节-pytest 集成 — conftest.py 中的 fixture 与 hook
开发语言·人工智能·python·功能测试·appium·测试用例·pytest
我叫张小白。9 小时前
劳动力招聘管理系统:全栈实战(Vue3+FastAPI+WebSocket+Dify)
websocket·vue·毕业设计·状态模式·fastapi·dify·智能体
Hui_AI7209 小时前
电商桌面自动化实战:用RPA实现抖店批量铺货
运维·开发语言·人工智能·自然语言处理·自动化·开源软件·rpa
人道领域9 小时前
【LeetCode刷题日记】递归与回溯实战 257.二叉树的所有路径——一篇文章彻底搞懂回溯
开发语言·python·算法·leetcode
Gofarlic_OMS9 小时前
Mastercam浮动许可利用率低:软件许可浪费,回收再分配
java·大数据·开发语言·架构·制造
吃好睡好便好9 小时前
在Matlab中用sphere( )函数绘制球面图
开发语言·前端·javascript·学习·算法·matlab·信息可视化
黑贝是条狗9 小时前
注册表破解chrome,edge阻止浏览器连接本地websocket
前端·javascript·数据库
lynnlovemin9 小时前
二分查找与二分答案算法详解(基于C++实现)
c语言·开发语言·算法·二分查找·二分答案