别再忽略 Promise 拒绝了!你的 Node.js 服务正在“静默自杀”

它不报错、不报警、不重启------直到凌晨三点用户投诉全线崩溃。

你是否写过这样的代码?

js 复制代码
app.post('/api/notify', (req, res) => {
  sendEmail(req.body.email); // 忘记 await,也没 catch
  res.status(200).send('OK');
});

async function sendEmail(email) {
  await smtpClient.send({ to: email, subject: 'Welcome!' });
}

看起来一切正常?

但只要 smtpClient.send() 抛出异常(比如网络超时、邮箱无效),一个未处理的 Promise 拒绝(Unhandled Rejection)就诞生了

而在 Node.js 中,这颗"定时炸弹"可能直接导致进程退出------悄无声息,不留痕迹。


为什么 Unhandled Rejection 如此危险?

从 Node.js v15 开始,官方默认行为已改为:

任何未处理的 Promise 拒绝都会导致进程直接退出!

是的,你没看错------不是警告,不是日志,是直接 kill 掉整个服务

即使你用 PM2、Docker 或 Kubernetes 托管,服务也会不断重启 → 崩溃 → 再重启,形成"死亡循环"。

更可怕的是:

  • 错误可能发生在非主流程(如埋点、日志上报、异步通知);
  • 用户请求已返回成功(res.send 已调用),你以为"没问题";
  • 实际后台任务失败,且无人知晓,直到数据丢失、订单漏发......

真实案例:一封邮件毁掉整站

某电商平台在用户下单后异步发送通知:

js 复制代码
orderService.create(order);
sendNotification(order.userId); // 忘记处理异常

某天第三方通知服务宕机,sendNotification 抛出错误。

由于未捕获,Node.js 进程退出。

K8s 自动重启 Pod,但新请求进来又触发同样逻辑 → 全站每分钟崩溃一次

运维查了两小时日志才发现:根本没有 error 日志!只有进程退出记录

根源?一个被忽略的 await


三大常见"漏网之鱼"

场景一:忘记 await 且不 catch

js 复制代码
// 危险!fire-and-forget 但未处理拒绝
fireAndForgetTask();

// 正确做法:至少 catch
fireAndForgetTask().catch(err => logger.warn('Task failed', err));

场景二:在 Promise.all 中部分失败

js 复制代码
// 只要一个 reject,整个 Promise.all 就 reject
// 如果外层没 catch,就是 unhandled rejection!
await Promise.all([
  fetchA(),
  fetchB(), // 假设这个失败了
  fetchC()
]);

解决方案:用 Promise.allSettled 或单独 catch 每个任务。

场景三:在事件监听器或定时器中抛出异步错误

js 复制代码
emitter.on('data', async (d) => {
  await process(d); // 如果 process 抛错,没人 catch!
});

这类错误完全脱离主调用栈,极易遗漏。


防御策略:四重保险,杜绝静默崩溃

第一重:全局监听(兜底)

在应用入口添加:

js 复制代码
process.on('unhandledRejection', (reason, promise) => {
  console.error('Unhandled Rejection at:', promise, 'reason:', reason);
  // 发送告警(如 Sentry、企业微信)
  // 注意:不要在这里 exit!先记录,再优雅关闭
});

// 同样建议监听 uncaughtException(同步错误)
process.on('uncaughtException', (err) => {
  console.error('Uncaught Exception:', err);
});

全局监听只是"最后防线",不能替代代码层面的错误处理


第二重:严格使用 await + try/catch

js 复制代码
app.post('/api/notify', async (req, res) => {
  try {
    await sendEmail(req.body.email);
    res.send('OK');
  } catch (err) {
    logger.error('Send email failed', err);
    res.status(500).send('Failed');
  }
});

第三重:对"fire-and-forget"任务显式处理

如果确实不需要等待结果(如打点、日志),也要 .catch

js 复制代码
// 明确表示"我知道可能失败,但我选择忽略"
sendAnalytics(event).catch(err => {
  // 至少记录,避免 unhandled rejection
  logger.debug('Analytics failed (ignored)', err);
});

第四重:ESLint + TypeScript 防呆

配置 ESLint 规则:

json 复制代码
{
  "rules": {
    "require-await": "error",
    "no-void": "warn"
  }
}

或者用 TypeScript 的 Promise<void> 显式标注,配合 lint 工具提醒未处理的 Promise。


终极心法:所有异步操作,必须有"归宿"

无论是:

  • API 调用
  • 数据库写入
  • 消息队列投递
  • 文件读写

只要它返回 Promise,你就必须回答一个问题:

"如果它失败了,谁来负责?"

如果没有答案,那就是隐患。


结语

Node.js 的优雅在于异步非阻塞,

但它的脆弱也藏在每一个被忽略的 reject 里。

别让一个小小的 await 缺失,

毁掉你精心构建的高可用服务。

从今天起,没有"无所谓"的异步调用,只有"已处理"和"待修复"

转发给你团队里那个总说"异步不用 catch"的人吧!


各位互联网搭子,要是这篇文章成功引起了你的注意,别犹豫,关注、点赞、评论、分享走一波,让我们把这份默契延续下去,一起在知识的海洋里乘风破浪!

相关推荐
树上有只程序猿8 分钟前
2026低代码选型指南,主流低代码开发平台排名出炉
前端·后端
橙某人16 分钟前
LogicFlow 小地图性能优化:从「实时克隆」到「占位缩略块」!🚀
前端·javascript·vue.js
高端章鱼哥28 分钟前
为什么说用OpenClaw对打工人来说“不划算”
前端·后端
大脸怪28 分钟前
告别 F12!前端开发者必备:一键管理 localStorage / Cookie / SessionStorage 神器
前端·后端·浏览器
Mr_Mao36 分钟前
我受够了混乱的 API 代码,所以我写了个框架
前端·api
小徐_233336 分钟前
向日葵 x AI:把远程控制封装成 MCP,让 AI 替我远程控制设备
前端·人工智能
boooooooom37 分钟前
讲清 Proxy + effect + track/trigger 流程
javascript·vue.js·面试
冴羽38 分钟前
来自顶级大佬 TypeScript 之父的 7 个启示
前端·typescript
leafyyuki1 小时前
在 Vue 项目中玩转 FullCalendar:从零搭建可交互的事件日历
前端·javascript·vue.js
决斗小饼干1 小时前
低代码平台工作流引擎设计:从状态机到智能流转的技术演进
前端·低代码·工作流引擎