NestJS中无法通过超时异常终止方法执行的问题
Nest API实现请求超时终止方法执行且不停止程序
问题描述
我有一个单端点的Nest API,需求是:请求60秒未响应时抛出RequestTimeoutException(),同时终止被调用方法的执行,但不能让整个程序停止。目前能成功给客户端返回超时异常,但抛出异常后,服务端的方法仍在后台继续执行。
试过两种方案,都有问题:
- 拦截器方案:能正常抛出超时异常,但无法终止正在执行的方法
- 定时器方案:会直接终止整个程序,不符合需求
解决方案:结合拦截器与可取消异步操作
RxJS的timeout只会取消Observable订阅,不会终止底层异步任务(比如await的Promise)。要实现终止方法执行,必须让服务的异步逻辑支持取消信号,比如使用AbortController。
1. 改造服务方法,支持取消信号
修改ChatbotService的generateResponse,接收AbortSignal参数,在异步操作中监听取消信号:
@Injectable() export class ChatbotService { async generateResponse(body: QueryModel, signal?: AbortSignal): Promise<any> { // 示例:模拟耗时异步操作,实际场景可能是调用外部API、数据库查询等 return new Promise((resolve, reject) => { const taskTimer = setTimeout(() => { resolve({ answer: '模拟生成的响应内容' }); }, 70000); // 设置为超过60秒,触发超时 // 监听取消信号,触发时清理任务并拒绝Promise if (signal) { signal.addEventListener('abort', () => { clearTimeout(taskTimer); reject(new RequestTimeoutException()); }); } }); } }
2. 改造拦截器,传递取消信号并触发超时
用rxjs的race操作符让超时信号和业务逻辑竞争,超时后触发AbortController终止任务:
@Injectable() export class TimeoutInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable<any> { const abortController = new AbortController(); // 60秒后触发超时,同时发送取消信号 const timeout$ = timer(60000).pipe( tap(() => abortController.abort()), map(() => { throw new RequestTimeoutException(); }) ); // 将取消信号挂载到请求对象,供控制器使用 const request = context.switchToHttp().getRequest(); request.abortSignal = abortController.signal; return race( next.handle(), timeout$ ).pipe( finalize(() => abortController.abort()) // 请求完成后主动取消,避免内存泄漏 ); } }
3. 修改控制器,传递取消信号到服务
注意:不要手动new ChatbotService,用Nest的依赖注入:
@Controller('chatbot') @UseInterceptors(TimeoutInterceptor) export class ChatbotController { constructor(private readonly chatbotService: ChatbotService) {} @Post('query') async postQuery(@Body() body: QueryModel, @Req() req: Request) { // 参数校验 if (!body.chatInfo?.chatId || !body.chatInfo?.userId || !body.question) { throw new BadRequestException(); } // 传递取消信号给服务方法 const response = await this.chatbotService.generateResponse(body, req['abortSignal']); return response; } }
为什么之前的方案失效?
- 拦截器方案:RxJS的
timeout只是取消了Observable的订阅,但await的Promise没有取消机制,服务方法会继续执行直到完成。 - 定时器方案:
setTimeout回调里抛出的异常属于宏任务,无法被当前的try/catch捕获,会直接导致Node.js进程崩溃。
关键注意事项
- 底层异步操作必须支持取消:如果是调用外部API,
fetch、axios等主流HTTP客户端都支持AbortSignal;如果是数据库操作,部分驱动也支持类似的取消机制。 - 依赖注入服务:始终通过构造函数注入服务,不要手动实例化,否则Nest的生命周期、依赖管理会失效。
- 清理资源:用
finalize确保请求完成后触发取消信号,避免残留的异步任务导致内存泄漏。
内容的提问来源于stack exchange,提问作者Daniel Pimentel
相关产品推荐
相关产品推荐

