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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 06:16:02