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

如何在NestJS拦截器的catchError中生成自定义响应?

NestJS拦截器自定义错误响应方案优化

问题背景

在NestJS中需通过拦截器捕获所有错误并生成自定义响应,无法使用全局异常过滤器——因存在记录请求/响应的日志拦截器,且错误处理必须先于日志拦截器执行(需为日志附加错误数据)。

当前错误处理拦截器代码

@Injectable()
export class UnknownExceptionsResponder implements NestInterceptor {
  constructor(private readonly httpAdapter: HttpServer) {}

  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next
        .handle()
        .pipe(
            catchError((error) => {
              const ctx = context.switchToHttp()
              const req = ctx.getRequest()
              req.info = Object.assign({ error }, req.info)

              let status = 500
              const response: any = {
                code: 'unknown_error',
                description: 'Internal server error'
              }

              if (error.exposeCustom_) {
                status = error.status || 500
                response.code = error.message || 'unknown_error'

                if (error.description) {
                  response.description = error.description
                }
                if (error.exposeMeta) {
                  response.meta = error.exposeMeta
                }
              }


              this.httpAdapter.reply(ctx.getResponse(), response, status);
              return EMPTY
            }),
        );
  }
}

现有方案的问题

  • 使用this.httpAdapter.reply后返回EMPTY:能得到正确响应,但触发ERROR [ExceptionsHandler] no elements in sequence错误
  • 返回scheduled([{error: error}], asyncScheduler):可触发日志拦截器,但出现ERROR [ExceptionsHandler] Cannot set headers after they are sent to the client错误
  • 移除reply调用后返回scheduled([response], asyncScheduler):响应体正确但状态码始终为200,不符合需求

解决方案

核心思路是手动设置响应状态码,返回包含自定义响应体的Observable,让NestJS完成响应发送流程,避免重复操作。

修改后的拦截器代码

@Injectable()
export class UnknownExceptionsResponder implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle().pipe(
      catchError((error) => {
        const ctx = context.switchToHttp();
        const req = ctx.getRequest();
        const res = ctx.getResponse();
        req.info = Object.assign({ error }, req.info);

        let status = 500;
        const response: any = {
          code: 'unknown_error',
          description: 'Internal server error',
        };

        if (error.exposeCustom_) {
          status = error.status || 500;
          response.code = error.message || 'unknown_error';
          if (error.description) {
            response.description = error.description;
          }
          if (error.exposeMeta) {
            response.meta = error.exposeMeta;
          }
        }

        // 手动设置响应状态码
        res.status(status);
        // 返回自定义响应体,让NestJS正常完成响应流程
        return of(response);
      }),
    );
  }
}

关键修改说明

  1. 移除httpAdapter.reply调用:避免手动发送响应导致后续流程重复设置响应头
  2. 手动设置状态码:通过res.status(status)直接修改响应对象的状态码
  3. 返回of(response):
    • 发出自定义响应体,让NestJS的响应流程正常完成,不会触发no elements in sequence错误
    • 确保日志拦截器能捕获到响应数据,完成日志记录
  4. 保证拦截器顺序:注册时将UnknownExceptionsResponder放在日志拦截器之前,确保错误处理先执行

拦截器注册示例

// 全局注册示例
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  // 先注册错误处理拦截器,再注册日志拦截器
  app.useGlobalInterceptors(
    new UnknownExceptionsResponder(),
    new LoggingInterceptor(),
  );
  await app.listen(3000);
}
bootstrap();

修改后既能生成带正确状态码的自定义响应,又能让日志拦截器正常工作,同时避免所有之前的错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 02:37:26