如何优雅关闭NestJS应用 触发unhandledRejection时正常终止进程
问题原因
你当前代码不生效的核心原因有两个:
unhandledRejection事件监听器注册时机太晚,放在了await app.listen()之后,应用启动过程中触发的未处理Promise rejection无法被捕获;如果是运行阶段的rejection触发后进程没退出,大概率是app.close()执行时被未释放的资源卡住,导致后续的process.exit(1)始终无法执行。- 异步的事件回调没有做异常兜底,一旦回调内部触发新的错误,会导致逻辑中断。
修复方案
修改后的完整代码如下:
import { INestApplication } from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; // 其余业务导入保持不变 /** * @desc - 启动应用,所有模块的组装入口 */ async function bootstrap() { let app: INestApplication; // 无依赖注入的日志实例 const logger = new LogService(); process.on('uncaughtException', (err: Error) => { logger.log('❌❌❌ ➡ ⬇⬇⬇ 发生未捕获异常错误 ⬇⬇⬇'); const error = { name: err.name, message: err.message, stack: err.stack, }; // 控制台打印错误 logger.error(err.message, error.stack); // 写入错误日志文件 logger.exception(error); setTimeout(() => { process.exit(1); }, 100); }); process.on('unhandledRejection', async (err: Error) => { logger.log('❌❌❌ ➡ ⬇⬇⬇ 发生未处理的Promise rejection错误 ⬇⬇⬇'); try { const error = { name: err.name, message: err.message, stack: err.stack, }; // 控制台打印错误 logger.error(error.message, error.stack); // 写入拒绝日志文件 logger.rejection(error); // 仅当应用实例已创建成功时关闭应用 if (app) { // 增加超时兜底,3秒未完成关闭则强制退出 const closeTimeout = setTimeout(() => process.exit(1), 3000); await app.close(); clearTimeout(closeTimeout); } } catch (e) { logger.error('优雅关闭过程出错,强制退出', e.stack); } finally { process.exit(1); } }); try { // 创建Nest应用实例 app = await NestFactory.create(AppModule, { logger, }); // 开启关闭钩子监听 app.enableShutdownHooks(); // 获取配置服务读取环境变量 const config_service = app.get(ConfigService); const HOST: string = config_service.get<string>(CONFIG.HOST); const PORT: number = config_service.get<number>(CONFIG.PORT); // 全局参数校验管道 app.useGlobalPipes(new ValidationPipe(VALIDATION_RULE)); // 全局异常过滤器 app.useGlobalFilters(new HttpExceptionFilter()); await app.listen(PORT); logger.log(`ℹℹℹ 服务已启动,监听地址 ${HOST}:${PORT} ℹℹℹ`); } catch (startErr) { logger.error('应用启动失败', startErr.stack); process.exit(1); } } bootstrap();
说明
- 方案同时兼容启动阶段和运行阶段的未处理rejection场景,启动阶段触发异常会直接退出,运行阶段会先尝试优雅关闭应用、释放端口和资源后再退出
- 如果开启了数据库、Redis等长连接,确保对应模块的
onModuleDestroy生命周期钩子中已经实现了连接销毁逻辑,否则app.close()可能会被未释放的长连接卡住
内容的提问来源于stack exchange,提问作者Peter Joshua
相关产品推荐
相关产品推荐

