unhandledrejection事件在Promise被拒绝且全程未被捕获时触发,浏览器默认打印警告,表明异步流程失控;需在链末尾统一catch、async函数外层try/catch,或用window.addEventListener兜底日志上报。

为什么 unhandledrejection 会触发全局报错
浏览器在 Promise 进入 reject 状态且全程未被 catch() 或 try/catch 捕获时,会派发 unhandledrejection 事件,并默认在控制台打印红色警告(Uncaught (in promise))。这不是语法错误,而是运行时环境的主动干预——它意味着你有一条异步路径彻底“失联”了。
常见诱因包括:
-
fetch()返回 404/500 响应但没检查response.ok,也未接.catch() -
async函数内部throw错误,调用方却没用try/catch包裹 -
Promise.all()中某个子 Promise 拒绝,而你只在链末尾加了.catch(),但中间某处又漏掉了 - 第三方库返回的 Promise 被你直接丢弃(比如只调用不 await、不接 then/catch)
catch() 必须出现在 Promise 链的末尾,不能中途断开
很多人以为在中间环节加个 .catch() 就万事大吉,其实只要它后面还有 .then(),错误就可能再次逃逸。例如:
fetch('/api/user')
.catch(err => console.error('fetch 失败')) // ❌ 这里捕获后,链仍继续
.then(res => res.json()) // 如果上一步 catch 返回了值,这行仍会执行;如果没返回,这里会收到 undefined
.then(data => doSomething(data)) // 此处若出错,再无 catcher
正确做法是确保链的终点有兜底:
- 要么把
.catch()放在最后一个.then()后面 - 要么在每个可能抛错的
.then()回调里自己try/catch - 对
async/await,必须在外层函数中用try/catch包裹整个 await 表达式
如何用 window.addEventListener('unhandledrejection') 做兜底
这是最后一道防线,不是替代方案。它只监听那些真正“漏网”的拒绝,适合做日志上报或降级提示,**不能用来修复逻辑**。
典型写法:
window.addEventListener('unhandledrejection', event => {
console.warn('未捕获 Promise 拒绝:', event.reason);
// 上报到监控系统
// Sentry.captureException(event.reason);
// event.preventDefault(); // 可选:阻止默认控制台输出(仅开发调试时建议关闭)
});
注意点:
- 该事件只在浏览器环境有效,Node.js 用
process.on('unhandledRejection') -
event.reason是原始 reject 值,可能是Error实例,也可能是字符串或任意值 - 不要在这里
throw新错误,否则可能引发二次unhandledrejection
最容易被忽略的陷阱:隐式 Promise 和非标准 reject
有些场景下,Promise 并不明显,但错误照样会逃逸:
-
setTimeout(() => Promise.reject(new Error('timeout')), 1000)—— 这个 Promise 完全没被接住 - Vue/React 组件中直接
await apiCall()却没 try/catch,组件卸载后 Promise 才 resolve/reject,状态已不可用 - 使用
Promise.resolve().then(() => { throw 'oops' }),错误会变成 rejected Promise,而非同步异常 - TypeScript 中类型守卫缺失:
catch(e)的e是unknown,直接访问e.message会报错,导致新异常
真正难处理的从来不是网络超时或 500,而是那些你以为“不会出错”的地方悄悄生成了未连接的 Promise。一旦漏掉一个,unhandledrejection 就会准时出现。

















