如何避免NestJS中@SubscribeMessage异常引发的过期ACK问题?
问题描述
我有一个Nest服务,其中包含若干@SubscribeMessage函数用于获取数据并返回给客户端。当客户端请求非法时,这些函数可能会抛出异常,但客户端调用socket.emitWithAck会一直挂起,直到socket断开连接,且此时也无法看到服务端抛出的错误。示例代码如下:
try { const result = socket.emitWithAck('throw-error', data); // do something with result } catch (e) { // show error to user }
用户永远看不到服务端的错误,只会在socket断开数小时后收到“Error: socket has been disconnected”错误。我希望客户端能看到@SubscribeMessage函数中抛出的错误,有没有比给每个@SubscribeMessage方法都套一个大型try/catch(在catch块中发送类似{ error: e }的内容)更优雅的方式?最好是只需配置一次的方案。
优雅解决方案:全局WebSocket异常过滤器
Nest提供了WebSocket异常过滤器能力,只需配置一次就能全局捕获所有@SubscribeMessage方法抛出的异常,并将错误信息返回给客户端,无需逐个方法添加try/catch。
步骤1:创建WebSocket异常过滤器
自定义过滤器实现WsExceptionFilter接口,统一处理异常并返回格式化的错误响应:
import { Catch, ArgumentsHost, WsExceptionFilter } from '@nestjs/common'; import { WsException } from '@nestjs/websockets'; import { Socket } from 'socket.io'; @Catch() export class AllWsExceptionsFilter implements WsExceptionFilter { catch(exception: unknown, host: ArgumentsHost) { const client = host.switchToWs().getClient<Socket>(); const data = host.switchToWs().getData(); // 区分自定义WsException和未知异常,格式化错误信息 const errorResponse = exception instanceof WsException ? { error: exception.getError() } : { error: '服务器内部错误', message: (exception as Error).message }; // 通过ack回调返回错误(emitWithAck依赖该机制) const callback = client._callbacks[data[0]]?.[0]; if (callback) { callback(errorResponse); // 清理回调避免内存泄漏 delete client._callbacks[data[0]]; } } }
步骤2:全局注册过滤器
在Nest根模块(如AppModule)中,将过滤器注册为全局WebSocket过滤器:
import { Module } from '@nestjs/common'; import { APP_FILTER } from '@nestjs/core'; import { AllWsExceptionsFilter } from './filters/all-ws-exceptions.filter'; @Module({ providers: [ { provide: APP_FILTER, useClass: AllWsExceptionsFilter, }, ], }) export class AppModule {}
步骤3:在业务代码中抛出异常
在@SubscribeMessage方法中直接抛出WsException(或自定义继承它的业务异常),过滤器会自动捕获并返回错误:
import { SubscribeMessage, WebSocketGateway, WsException } from '@nestjs/websockets'; @WebSocketGateway() export class AppGateway { @SubscribeMessage('throw-error') handleThrowError(client: any, data: any) { if (!data.valid) { throw new WsException('请求参数非法'); } return { result: 'success' }; } }
客户端接收错误
此时客户端的emitWithAck会正常捕获到错误:
try { const result = await socket.emitWithAck('throw-error', { valid: false }); // 处理正常结果 } catch (e) { // 这里会收到服务端返回的错误:{ error: '请求参数非法' } console.error('请求失败:', e); }
关键说明
- 过滤器通过
ArgumentsHost获取WebSocket客户端、请求数据和ack回调,确保错误通过ack机制返回,避免请求挂起。 - 全局注册后,所有
@SubscribeMessage方法的异常都会被统一处理,无需逐个添加try/catch。 - 可根据业务需求扩展过滤器,比如添加错误日志、区分不同异常类型返回不同格式响应。
内容的提问来源于stack exchange,提问作者Jan Thürmann
相关产品推荐
相关产品推荐

