本文基于实际代码实例,详细拆解RxJS、生成器函数的基础知识、代码实现逻辑,以及两者结合Nest.js、LangChain实现AI流式输出的完整流程。学习重点聚焦代码语法、核心原理与实际应用场景,兼顾基础概念与工程实践,适合有一定JavaScript/TypeScript基础、希望掌握异步编程与流式输出技术的开发者。全文围绕代码展开解析,确保每个知识点都有对应的代码支撑,帮助深入理解技术底层逻辑。
一、生成器函数:异步编程的过渡方案(async/await前身)
生成器函数是ES6引入的一种特殊函数,它打破了传统函数“从头执行到尾、无法暂停”的特性,允许函数在执行过程中暂停和恢复,是异步编程的重要过渡方案,也是async/await语法的前身。下面从基础语法、工作原理、代码解析三个维度展开学习。
1.1 生成器函数的基础语法
生成器函数与普通函数的核心区别有三点:
- 函数声明时加
*(function关键字后、函数名前); - 函数内部使用
yield关键字暂停执行; - 调用生成器函数不会立即执行函数体,而是返回一个迭代器对象,通过迭代器的
next()方法控制函数的暂停与恢复。
基础语法模板:
// 生成器函数声明(三种方式均可)
function* generatorFunction() {
yield '暂停点1'; // yield:暂停执行,返回当前值
yield '暂停点2';
return '执行结束'; // return:结束生成器,返回最终值
}
// 调用生成器函数,返回迭代器对象(不执行函数体)
const iterator = generatorFunction();
// 通过next()方法控制执行
console.log(iterator.next()); // { value: '暂停点1', done: false }
console.log(iterator.next()); // { value: '暂停点2', done: false }
console.log(iterator.next()); // { value: '执行结束', done: true }
关键说明:
function*:生成器函数的标识,不可省略。function *fruitGenerator()与function* fruitGenerator()等价。yield:暂停关键字,将yield后面的值作为next()方法返回值的value属性。每次调用next(),函数从上次暂停的位置继续执行,直到遇到下一个yield或return。- 迭代器对象:生成器函数的返回值,具备
next()方法。next()返回一个对象,包含value(当前暂停/结束时的返回值)和done(布尔值,true表示函数执行完毕)。 return的作用:当函数执行到return时,生成器结束,此时next()返回的done为true,value为return的值;若没有return,最终next()返回{ value: undefined, done: true }。
1.2 生成器函数代码解析(实战示例)
通过一个水果生成器的示例,可以清晰理解生成器的执行流程:
function* fruitGenerator() {
console.log('开始生成水果 start');
yield 'apple'; // 第一个暂停点,返回'apple'
console.log('继续生产 apple');
yield 'banana'; // 第二个暂停点,返回'banana'
console.log('生成了 banana 生成结束');
return '没有更多水果'; // 执行结束,返回最终值
}
const fruitMachine = fruitGenerator(); // 此时函数体未执行
console.log(fruitMachine.next());
// 输出1:开始生成水果 start
// 输出2:{ value: 'apple', done: false }
console.log(fruitMachine.next());
// 输出1:继续生产 apple
// 输出2:{ value: 'banana', done: false }
console.log(fruitMachine.next());
// 输出1:生成了 banana 生成结束
// 输出2:{ value: '没有更多水果', done: true }
console.log(fruitMachine.next()); // { value: undefined, done: true }
执行流程总结:
- 生成器函数调用时不会立即执行函数体,只返回迭代器对象。
- 每次调用
next(),函数从上一次暂停的位置继续执行,直到遇到下一个yield或return:- 遇到
yield:暂停,将yield后的值作为value返回,done为false。 - 遇到
return:执行完return前的代码,将返回值作为value返回,done为true,函数终止。
- 遇到
- 当
done为true后,后续next()调用不再执行函数体,value始终为undefined。
1.3 生成器函数的核心作用(异步编程)
生成器函数的核心价值在于解决异步编程的“回调地狱”问题,为async/await提供了思路。在async/await出现之前,通过生成器函数配合Promise,可以让异步代码的写法更接近同步代码。
结合Promise的异步示例:
// 模拟异步请求
function getUserInfo(userId) {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ id: userId, name: '张三', age: 20 });
}, 1000);
});
}
// 生成器函数:用yield暂停异步操作
function* getAsyncData() {
console.log('开始获取用户信息');
const userInfo = yield getUserInfo('001'); // 暂停,等待Promise resolve
console.log('获取到用户信息:', userInfo);
return '异步操作完成';
}
const iterator = getAsyncData();
const promise = iterator.next().value; // 第一次next(),返回Promise
promise.then((userInfo) => {
iterator.next(userInfo); // 将异步结果传回,恢复执行
});
// 输出:开始获取用户信息 → 等待1秒 → 获取到用户信息:{ id: '001', name: '张三', age: 20 }
关键解析:
yield后面可以跟Promise对象,此时next()返回的value就是这个Promise。- 当Promise resolve后,调用
next(resolvedValue),传入的值会作为yield表达式的返回值,赋值给变量(如上例中的userInfo),从而实现异步操作完成后继续执行后续代码。 - 这正是
async/await的本质——async函数是生成器函数的语法糖,await等价于yield,并且引擎自动处理迭代器的next()调用和Promise状态。
1.4 生成器函数的注意事项
yield关键字只能在生成器函数内部使用,在普通函数中使用会报语法错误。- 生成器函数不能作为构造函数使用(不能用
new关键字调用)。 next()方法可以传入参数,该参数会作为上一个yield表达式的返回值。- 生成器函数可配合
for...of循环迭代,循环会自动调用next()直到done为true,且会忽略return的值:for (const fruit of fruitGenerator()) { console.log(fruit); // 输出:apple, banana(不输出return的值) }
二、RxJS:以数据流方式处理异步事件
RxJS(ReactiveX for JavaScript)基于观察者模式和迭代器模式,将异步事件(如用户输入、网络请求、SSE流)封装成可观察对象(Observable),通过一系列操作符(如map、pipe)处理数据流,非常适合处理“连续发生的异步事件”。
2.1 RxJS核心概念
RxJS的核心概念包括四个:可观察对象(Observable)、观察者(Observer)、订阅(Subscription)、操作符(Operator)。
- Observable:代表一个可被观察的数据流,可以发射零个、一个或多个值,也可以发射错误或完成信号。Observable是惰性的,只有在被订阅(
subscribe)后才会开始发射数据。 - Observer:一个对象,包含
next、error、complete三个方法,用于接收Observable发射的数据。 - Subscription:
Observable.subscribe()的返回值,用于取消订阅(unsubscribe())或合并多个订阅。 - Operator:用于对数据流进行转换、过滤、组合等操作,通过
pipe()方法串联。
2.2 RxJS基础代码解析
2.2.1 示例1:from()创建Observable(将数组转为数据流)
import { from } from 'rxjs';
const stream = from([1, 2, 3]);
stream.subscribe(v => console.log(v));
// 输出:1 2 3
from将数组(或其他可迭代对象)转换为Observable,数组中的每个元素会被依次发射。- 订阅后,Observable依次发射
1、2、3,每发射一个就执行next回调,发射完成后自动触发complete。
2.2.2 示例2:map操作符处理数据流(转换数据)
import { from, map } from 'rxjs';
from([1, 2, 3])
.pipe(map(x => x * 2))
.subscribe(v => console.log(v));
// 输出:2 4 6
map操作符对每个发射的数据进行转换,返回一个新的Observable。pipe()方法用于串联多个操作符,形成数据处理流水线。- 区别于数组的
map:数组的map是一次性处理所有元素返回新数组;RxJS的map是流式处理,每接收一个数据立即处理并发射。
2.2.3 示例3:手动创建Observable(new Observable())
import { Observable } from 'rxjs';
const stream = new Observable((subscriber) => {
subscriber.next('hello');
subscriber.next('world');
subscriber.complete();
subscriber.next('不会被发射'); // complete后不再发射
});
stream.subscribe((value) => console.log(value));
// 输出:hello world
new Observable的回调参数subscriber是观察者对象,通过它的next、error、complete方法发射数据或信号。- 调用
complete()后,Observable终止,后续next不再生效。
2.3 RxJS处理异步事件的优势
相比其他异步方式(callback、Promise、生成器函数),RxJS的核心优势在于处理连续异步事件。
2.3.1 对比callback:解决回调地狱
// callback方式(嵌套过深)
getUserInfo('001', (user) => {
getOrderList(user.id, (orders) => {
getOrderDetail(orders[0].id, (detail) => console.log(detail));
});
});
// RxJS方式(流式链式调用)
import { from } from 'rxjs';
import { switchMap } from 'rxjs/operators';
const getUser$ = from(getUserInfoPromise('001'));
const getOrders$ = (userId) => from(getOrderListPromise(userId));
const getDetail$ = (orderId) => from(getOrderDetailPromise(orderId));
getUser$
.pipe(switchMap(user => getOrders$(user.id)))
.pipe(switchMap(orders => getDetail$(orders[0].id)))
.subscribe(detail => console.log(detail));
2.3.2 对比Promise:处理连续事件
Promise只能处理一次性异步任务,而RxJS擅长处理连续事件,例如输入框实时输入:
import { fromEvent } from 'rxjs';
import { filter, debounceTime, map } from 'rxjs/operators';
fromEvent(document.getElementById('input'), 'input')
.pipe(
map(event => event.target.value),
filter(value => value.length >= 3),
debounceTime(500)
)
.subscribe(value => console.log('处理输入:', value));
2.3.3 对比生成器函数:更强大的数据流控制
生成器函数需要手动管理迭代器的next()调用,而RxJS提供了丰富的操作符,无需手动管理,能轻松实现过滤、延迟、合并等复杂逻辑。
2.4 常用操作符补充(与AI流式相关)
from:将可迭代对象(数组、Promise、迭代器)转为Observable。map:转换数据。switchMap:切换到新的Observable,并取消之前的订阅(适合输入框实时搜索等场景)。tap:执行副作用(如打印日志),不改变数据流。
三、AI流式输出:Nest.js + RxJS + LangChain 实现(核心实战)
本节基于Nest.js、RxJS、LangChain实现AI流式输出(SSE接口),这是当前后端开发中AI交互的主流方案(如ChatGPT的流式回复)。我们将从核心原理、模块代码解析、流式流程拆解、关键细节四个层面深入理解。
3.1 AI流式输出核心原理
- 后端使用Nest.js的
@Sse装饰器实现SSE(Server-Sent Events)接口,SSE是服务器向客户端单向推送数据流的协议,适合流式输出。 - 通过LangChain调用大模型,开启
streaming: true,让大模型分块(chunk)返回结果。 - 使用RxJS的
from操作符将LangChain返回的异步可迭代对象(AsyncIterable)转为Observable,再通过map操作符将每个chunk转换为前端需要的格式({ data: chunk })。 - 通过异步生成器函数(
async*)实现agent loop,处理大模型的工具调用(如查询用户信息),实现“边生成、边判断、边输出”。
SSE接口必需响应头(Nest.js的@Sse自动设置):
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
Transfer-Encoding: chunked
3.2 核心代码模块解析
3.2.1 AppModule(根模块):全局配置
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { AiModule } from './ai/ai.module';
import { ConfigModule } from '@nestjs/config';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
envFilePath: '.env',
}),
AiModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
ConfigModule读取.env文件中的环境变量(如OPENAI_API_KEY、MODEL_NAME),isGlobal: true表示全局可用。
3.2.2 AiModule(AI模块):注入大模型实例
import { Module } from '@nestjs/common';
import { AiService } from './ai.service';
import { AiController } from './ai.controller';
import { ChatOpenAI } from '@langchain/openai';
import { ConfigService } from '@nestjs/config';
@Module({
controllers: [AiController],
providers: [
AiService,
{
provide: 'CHAT_MODEL',
useFactory: (configService: ConfigService) => {
return new ChatOpenAI({
model: configService.get('MODEL_NAME'),
apiKey: configService.get('OPENAI_API_KEY'),
configuration: {
baseURL: configService.get('OPENAI_BASE_URL'),
},
streaming: true, // 必须设为true,启用流式模式
});
},
inject: [ConfigService],
},
],
})
export class AiModule {}
- 使用工厂模式动态创建
ChatOpenAI实例,依赖ConfigService获取环境变量。 streaming: true是流式输出的关键,此时stream()方法返回异步可迭代对象。
3.2.3 AiController(控制器):实现SSE接口
import { Controller, Get, Query, Sse } from '@nestjs/common';
import { AiService } from './ai.service';
import { from, Observable, map } from 'rxjs';
@Controller('ai')
export class AiController {
constructor(private readonly aiService: AiService) {}
@Sse('chat/stream')
chatStream(@Query('query') query: string): Observable<MessageEvent> {
const stream = this.aiService.runChainStream(query);
return from(stream).pipe(
map((chunk) => ({ data: chunk }))
) as Observable<MessageEvent>;
}
}
@Sse('chat/stream'):声明SSE接口,路由为/ai/chat/stream,自动设置SSE响应头。from(stream):将AsyncIterable转为Observable。map((chunk) => ({ data: chunk })):转换为SSE要求的{ data: chunk }格式。
3.2.4 AiService(服务层):核心流式逻辑 + 工具调用
3.2.4.1 工具定义:queryUserTool(查询用户信息)
import { tool } from '@langchain/core/tools';
import { z } from 'zod';
const queryUserArgsSchema = z.object({
userId: z.string().describe('用户ID,例如: 001,002,003'),
});
type QueryUserArgs = { userId: string };
const database = {
users: {
'001': { id: '001', name: '张三', email: 'zhangsan@example.com', role: 'admin' },
'002': { id: '002', name: '李四', email: 'lisi@example.com', role: 'user' },
'003': { id: '003', name: '王五', email: 'wangwu@example.com', role: 'user' },
},
};
const queryUserTool = tool(
async ({ userId }: QueryUserArgs) => {
const user = database.users[userId];
if (!user) return `用户${userId}不存在`;
return `用户信息:\n 用户ID:${user.id}\n 姓名:${user.name}\n 邮箱:${user.email}\n 角色:${user.role}`;
},
{
name: 'query_user',
description: '查询数据库中的用户消息,输入用户ID,返回该用户的详细信息(姓名、邮箱、角色)',
schema: queryUserArgsSchema,
}
);
3.2.4.2 服务初始化:绑定大模型与工具
import { Injectable, Inject } from '@nestjs/common';
import { ChatOpenAI } from '@langchain/openai';
import { Runnable } from '@langchain/core/runnables';
import { BaseMessage, AIMessage, SystemMessage, HumanMessage, ToolMessage, AIMessageChunk } from '@langchain/core/messages';
@Injectable()
export class AiService {
private readonly modelWithTools: Runnable<BaseMessage[], AIMessage>;
constructor(@Inject('CHAT_MODEL') model: ChatOpenAI) {
this.modelWithTools = model.bindTools([queryUserTool]);
}
// 流式调用:异步生成器函数
async *runChainStream(query: string): AsyncIterable<string> {
const messages: BaseMessage[] = [
new SystemMessage('你是一个智能助手,可以在需要时调用工具(如query_user)来查询用户信息,再用结果回答用户的问题。'),
new HumanMessage(query),
];
while (true) {
const stream = await this.modelWithTools.stream(messages);
let fullAIMessage: AIMessageChunk | null = null;
for await (const chunk of stream as AsyncIterable<AIMessageChunk>) {
fullAIMessage = fullAIMessage ? fullAIMessage.concat(chunk) : chunk;
const hasToolCallChunk = !!fullAIMessage.tool_call_chunks && fullAIMessage.tool_call_chunks.length > 0;
// 若当前chunk不包含工具调用且有内容,则输出给前端
if (!hasToolCallChunk && chunk.content) {
yield chunk.content as string;
}
}
if (!fullAIMessage) break;
const toolCalls = fullAIMessage.tool_calls;
if (!toolCalls || toolCalls.length === 0) break;
// 处理工具调用
for (const toolCall of toolCalls) {
try {
if (toolCall.name !== 'query_user') {
messages.push(new ToolMessage({
toolCallId: toolCall.id,
content: `工具${toolCall.name}未定义,无法执行`,
}));
continue;
}
const toolArgs = toolCall.args as QueryUserArgs;
const toolResult = await queryUserTool.invoke(toolArgs);
messages.push(new ToolMessage({
toolCallId: toolCall.id,
content: toolResult,
}));
} catch (error) {
messages.push(new ToolMessage({
toolCallId: toolCall.id,
content: `执行工具${toolCall.name}失败:${(error as Error).message}`,
}));
}
}
// 循环继续,将包含工具结果的messages再次传入大模型
}
}
}
代码核心逻辑解析:
runChainStream是一个异步生成器函数(async*),内部实现agent loop:- 初始化消息数组,包含系统提示和用户提问。
- 进入无限循环,每次调用大模型的
stream()获取流式响应。 - 遍历响应的每个chunk,合并得到完整的
AIMessageChunk。 - 判断当前chunk是否包含工具调用(
tool_call_chunks)。若不包含且有内容,则yield输出给前端。 - 流结束后,检查完整消息中的
tool_calls。若无工具调用,则终止循环;若有,则执行对应工具。 - 将工具执行结果封装为
ToolMessage并加入messages数组,然后继续循环,让大模型基于工具结果生成最终回答。
- 修正点:原判断条件
if (hasToolCallChunk && chunk.content)逻辑错误,此处修正为if (!hasToolCallChunk && chunk.content),确保只在非工具调用时输出内容。 - 工具调用支持异常捕获,保证循环不因单个工具失败而中断。
- 整个流程实现了:用户提问 → 大模型流式响应 → 判断是否需要工具 → 执行工具 → 大模型基于工具结果继续流式输出 → 完成。
四、全文总结
本文围绕代码实例,从基础到实战,系统拆解了生成器函数、RxJS的核心用法,以及两者结合Nest.js、LangChain实现AI流式输出的完整流程。核心要点总结如下:
-
生成器函数:通过
function*和yield实现函数的暂停与恢复,是async/await的前身。其惰性执行特性配合Promise可解决回调地狱,也是理解AI流式中异步生成器函数的基础。 -
RxJS:基于观察者模式和迭代器模式,将异步事件封装为Observable,通过操作符(
from、map、switchMap等)实现数据流的转换与组合。它擅长处理连续异步事件(如SSE流、实时输入),无需手动管理数据流,是AI流式输出的核心依赖。 -
AI流式输出实战:基于Nest.js + RxJS + LangChain的主流方案:
- Nest.js的
@Sse装饰器简化SSE协议实现。 - LangChain开启
streaming: true,通过异步生成器函数实现agent loop,处理工具调用。 - RxJS的
from将异步可迭代对象转为Observable,并通过map格式化输出。 - 最终形成“用户提问 → 大模型流式响应 → 工具调用(可选)→ 流式输出”的高体验闭环。
- Nest.js的
生成器函数提供了异步暂停/恢复的基础能力,RxJS解决了数据流的高效处理问题,两者结合Nest.js和LangChain,共同实现了生产级的AI流式输出。本文所有知识点均围绕代码展开,重点突出语法细节、逻辑原理与实战应用,适合开发者快速掌握异步编程与流式输出技术,为后续开发AI交互类应用提供坚实的代码与理论支撑。