实习生提交的Async/Await未处理拒绝,竟让服务崩溃?

实习生提交的Async/Await未处理拒绝,竟让服务崩溃?
最新回答
姊‘妝濃孒

2020-11-08 04:36:12

答案:未处理的Async/Await拒绝会导致Node.js进程崩溃,尤其在Node.js v15+版本中,未捕获的Promise拒绝会直接触发进程退出。实习生小王的代码因未包裹try-catch,当数据库查询返回无效数据(如coupon为undefined)时,访问coupon.id会抛出异常,导致进程崩溃。

1. 问题根源分析
  • 未处理的Promise拒绝:小王的代码中,await db.query()可能因数据库错误或无效输入抛出异常,但未用try-catch捕获,导致未处理的Promise拒绝。
  • Node.js版本行为差异

    Node.js < v14:仅打印警告,进程继续运行。

    Node.js ≥ v15:未处理拒绝视为未捕获异常,触发进程退出(退出码1)。

  • 进程崩溃链条:await抛出异常 → 无.catch()处理 → 触发unhandledRejection事件 → Node.js默认行为导致进程退出 → PM2检测到退出后尝试重启,高频崩溃时放弃治疗。
2. 解决方案青铜方案:手动添加try-catchrouter.post('/use-coupon', async (req, res) => { try { const { code } = req.body; const coupon = await db.query('SELECT * FROM coupons WHERE code = ?', [code]); if (!coupon) throw new Error('优惠券不存在'); await db.query('UPDATE coupons SET status = "used" WHERE id = ?', [coupon.id]); res.json({ success: true }); } catch (err) { console.error(err); res.status(500).json({ error: '服务异常' }); }});黄金方案:Express全局错误中间件// 全局错误处理中间件(需放在路由之后)app.use((err, req, res, next) => { Sentry.captureException(err); // 上报异常 metrics.increment('server.error'); // 监控计数 res.status(500).send('服务端错误');});王者方案:进程级安全网// 捕获未处理的Promise拒绝process.on('unhandledRejection', (reason, promise) => { Sentry.withScope(scope => { scope.setTag('crash_type', 'unhandled_rejection'); Sentry.captureException(reason); }); logger.fatal('未捕获拒绝,但保持进程存活:', reason); // 可选:阻止进程退出(根据业务需求)});PM2托底配置(ecosystem.config.js)module.exports = { apps: [{ name: 'server', script: 'app.js', max_restarts: 5, // 最大重启次数 min_uptime: '60s' // 最小存活时间 }]};3. 预防措施团队规范
  • Code Review红牌项

    所有async函数必须显式包含try-catch。

    禁止裸写await(除非外层有catch)。

    全局错误中间件必须覆盖所有路由。

监控与测试
  • Sentry集成:Sentry.init({ dsn: 'YOUR_DSN', integrations: [new Sentry.Integrations.Express({ app })], beforeSend(event) { if (event.exception) return event; // 过滤非异常事件 }});
  • 混沌测试

    在测试环境注入随机错误(如Mock数据库抛出拒绝或使用chaos-node模块)。

    命令示例:npm test -- --chaos。

4. 延伸思考
  • 开发效率与异常处理的平衡:团队可通过代码模板、ESLint规则(如eslint-plugin-promise)强制要求错误处理,同时利用AOP(面向切面编程)封装通用错误处理逻辑。
  • 防御性编码秘籍

    使用TypeScript或JSDoc明确函数可能抛出的异常类型。

    对第三方库调用添加超时和重试机制(如p-retry)。

    关键操作前添加参数校验(如Joi或class-validator)。

总结

Async/Await的异常处理是线上服务稳定性的基石。通过显式捕获、全局中间件和进程级兜底,结合监控与测试,可避免因未处理拒绝导致的服务崩溃。正如小王在复盘会上所言:“异常处理不是可选项,而是生存法则。”